laforge has submitted this change. (
https://gerrit.osmocom.org/c/pysim/+/39745?usp=email )
Change subject: personalization: refactor AlgorithmID, K, Opc
......................................................................
personalization: refactor AlgorithmID, K, Opc
Refactor AlgorithmID, K, Opc to the new ConfigurableParameter
implementation style.
K and Opc use a common abstract BinaryParam.
Note from the future: AlgorithmID so far takes "raw" int values, but
will turn to be an "enum" parameter with predefined meaningful strings
in I71c2ec1b753c66cb577436944634f32792353240
Change-Id: I6296fdcfd5d2ed313c4aade57ff43cc362375848
---
M pySim/esim/saip/personalization.py
1 file changed, 98 insertions(+), 40 deletions(-)
Approvals:
Jenkins Builder: Verified
laforge: Looks good to me, approved
neels: Looks good to me, but someone else must approve
diff --git a/pySim/esim/saip/personalization.py
b/pySim/esim/saip/personalization.py
index d1998dc..c715118 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -254,6 +254,51 @@
# a DecimalHexParam subclass expects the apply_val() input to be a
bytes instance ready for the pes
return h2b(val)
+class IntegerParam(ConfigurableParameter):
+ allow_types = (str, int)
+ allow_chars = '0123456789'
+
+ # two integers, if the resulting int should be range limited
+ min_val = None
+ max_val = None
+
+ @classmethod
+ def validate_val(cls, val):
+ val = super().validate_val(val)
+ val = int(val)
+ exceeds_limits = False
+ if cls.min_val is not None:
+ if val < cls.min_val:
+ exceeds_limits = True
+ if cls.max_val is not None:
+ if val > cls.max_val:
+ exceeds_limits = True
+ if exceeds_limits:
+ raise ValueError(f'Value {val} is out of range, must be
[{cls.min_val}..{cls.max_val}]')
+ return val
+
+class BinaryParam(ConfigurableParameter):
+ allow_types = (str, io.BytesIO, bytes, bytearray)
+ allow_chars = '0123456789abcdefABCDEF'
+ strip_chars = ' \t\r\n'
+
+ @classmethod
+ def validate_val(cls, val):
+ # take care that min_len and max_len are applied to the binary length
by converting to bytes first
+ if isinstance(val, str):
+ if cls.strip_chars is not None:
+ val = ''.join(c for c in val if c not in cls.strip_chars)
+ if len(val) & 1:
+ raise ValueError('Invalid hexadecimal string, must have even
number of digits:'
+ f' {val!r} {len(val)=}')
+ try:
+ val = h2b(val)
+ except ValueError as e:
+ raise ValueError(f'Invalid hexadecimal string: {val!r}
{len(val)=}') from e
+
+ val = super().validate_val(val)
+ return bytes(val)
+
class Iccid(DecimalParam):
"""ICCID Parameter. Input: string of decimal digits.
@@ -513,42 +558,60 @@
class Adm2(Pin):
keyReference = 0x0B
+class AlgoConfig(ConfigurableParameter):
+ algo_config_key = None
-class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
- """Configurable Algorithm parameter."""
- key = None
- def validate(self):
- if not isinstance(self.input_value, (io.BytesIO, bytes, bytearray)):
- raise ValueError('Value must be of bytes-like type')
- self.value = self.input_value
- def apply(self, pes: ProfileElementSequence):
+ @classmethod
+ def apply_val(cls, pes: ProfileElementSequence, val):
+ found = 0
for pe in pes.get_pes_for_type('akaParameter'):
algoConfiguration = pe.decoded['algoConfiguration']
if algoConfiguration[0] != 'algoParameter':
continue
- algoConfiguration[1][self.key] = self.value
+ algoConfiguration[1][cls.algo_config_key] = val
+ found += 1
+ if not found:
+ raise ValueError('input template UPP has unexpected structure:'
+ f' {cls.__name__} cannot find algoParameter with
key={cls.algo_config_key}')
-class K(AlgoConfig, key='key'):
- pass
-class Opc(AlgoConfig, key='opc'):
- pass
-class AlgorithmID(AlgoConfig, key='algorithmID'):
- def validate(self):
- if self.input_value not in [1, 2, 3]:
- raise ValueError('Invalid algorithmID %s' % (self.input_value))
- self.value = self.input_value
-class MilenageRotationConstants(AlgoConfig, key='rotationConstants'):
+class AlgorithmID(DecimalParam, AlgoConfig):
+ algo_config_key = 'algorithmID'
+ allow_len = 1
+
+ @classmethod
+ def validate_val(cls, val):
+ val = super().validate_val(val)
+ val = int(val)
+ valid = (1, 2, 3)
+ if val not in valid:
+ raise ValueError(f'Invalid algorithmID {val!r}, must be one of
{valid}')
+ return val
+
+class K(BinaryParam, AlgoConfig):
+ """use validate_val() from BinaryParam, and apply_val() from AlgoConfig"""
+ algo_config_key = 'key'
+ allow_len = (128 // 8, 256 // 8) # length in bytes (from BinaryParam);
TUAK also allows 256 bit
+
+class Opc(K):
+ algo_config_key = 'opc'
+
+class MilenageRotationConstants(BinaryParam, AlgoConfig):
"""rotation constants r1,r2,r3,r4,r5 of Milenage, Range 0..127. See 3GPP
TS 35.206 Sections 2.3 + 5.3.
Provided as octet-string concatenation of all 5 constants. Expects a
bytes-like object of length 5, with
each byte in the range of 0..127. The default value by 3GPP is
'4000204060' (hex notation)"""
- def validate(self):
- super().validate()
- if len(self.input_value) != 5:
- raise ValueError('Length of value must be 5 octets')
- for r in self.input_value:
- if r > 127:
- raise ValueError('r values must be between 0 and 127')
-class MilenageXoringConstants(AlgoConfig, key='xoringConstants'):
+ algo_config_key = 'rotationConstants'
+ allow_len = 5 # length in bytes (from BinaryParam)
+
+ @classmethod
+ def validate_val(cls, val):
+ "allow_len checks the length, this in addition checks the value range"
+ val = super().validate_val(val)
+ assert isinstance(val, bytes)
+ if any(r > 127 for r in val):
+ raise ValueError('r values must be in the range 0..127')
+ return val
+
+class MilenageXoringConstants(BinaryParam, AlgoConfig):
"""XOR-ing constants c1,c2,c3,c4,c5 of Milenage, 128bit each. See 3GPP TS
35.206 Sections 2.3 + 5.3.
Provided as octet-string concatenation of all 5 constants. The default
value by 3GPP is the concetenation
of:
@@ -558,16 +621,11 @@
00000000000000000000000000000004
00000000000000000000000000000008
"""
- def validate(self):
- super().validate()
- if len(self.input_value) != 80:
- raise ValueError('Length of value must be 80 octets')
-class TuakNumberOfKeccak(AlgoConfig, key='numberOfKeccak'):
- """Number of iterations of Keccak-f[1600] permutation as recomended by
Section 7.2 of 3GPP TS 35.231.
- The default value by 3GPP is 1."""
- def validate(self):
- if not isinstance(self.input_value, int):
- raise ValueError('Value must be an integer')
- if self.input_value < 1 or self.input_value > 255:
- raise ValueError('Value must be an integer between 1 and 255')
- self.value = self.input_value
+ algo_config_key = 'xoringConstants'
+ allow_len = 80 # length in bytes (from BinaryParam)
+
+class TuakNumberOfKeccak(IntegerParam, AlgoConfig):
+ """Number of iterations of Keccak-f[1600] permutation as recomended by
Section 7.2 of 3GPP TS 35.231"""
+ algo_config_key = 'numberOfKeccak'
+ min_val = 1
+ max_val = 255
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/39745?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I6296fdcfd5d2ed313c4aade57ff43cc362375848
Gerrit-Change-Number: 39745
Gerrit-PatchSet: 15
Gerrit-Owner: neels <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: neels <[email protected]>