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
This commit is contained in:
Neels Hofmeyr
2025-03-01 01:28:23 +01:00
committed by laforge
parent d5b570b01d
commit ae656c66a3

View File

@@ -254,6 +254,51 @@ class DecimalHexParam(DecimalParam):
# a DecimalHexParam subclass expects the apply_val() input to be a bytes instance ready for the pes # a DecimalHexParam subclass expects the apply_val() input to be a bytes instance ready for the pes
return h2b(val) 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): class Iccid(DecimalParam):
"""ICCID Parameter. Input: string of decimal digits. """ICCID Parameter. Input: string of decimal digits.
@@ -513,42 +558,60 @@ class Adm1(Pin):
class Adm2(Pin): class Adm2(Pin):
keyReference = 0x0B keyReference = 0x0B
class AlgoConfig(ConfigurableParameter):
algo_config_key = None
class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta): @classmethod
"""Configurable Algorithm parameter.""" def apply_val(cls, pes: ProfileElementSequence, val):
key = None found = 0
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):
for pe in pes.get_pes_for_type('akaParameter'): for pe in pes.get_pes_for_type('akaParameter'):
algoConfiguration = pe.decoded['algoConfiguration'] algoConfiguration = pe.decoded['algoConfiguration']
if algoConfiguration[0] != 'algoParameter': if algoConfiguration[0] != 'algoParameter':
continue 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'): class AlgorithmID(DecimalParam, AlgoConfig):
pass algo_config_key = 'algorithmID'
class Opc(AlgoConfig, key='opc'): allow_len = 1
pass
class AlgorithmID(AlgoConfig, key='algorithmID'): @classmethod
def validate(self): def validate_val(cls, val):
if self.input_value not in [1, 2, 3]: val = super().validate_val(val)
raise ValueError('Invalid algorithmID %s' % (self.input_value)) val = int(val)
self.value = self.input_value valid = (1, 2, 3)
class MilenageRotationConstants(AlgoConfig, key='rotationConstants'): 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. """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 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)""" each byte in the range of 0..127. The default value by 3GPP is '4000204060' (hex notation)"""
def validate(self): algo_config_key = 'rotationConstants'
super().validate() allow_len = 5 # length in bytes (from BinaryParam)
if len(self.input_value) != 5:
raise ValueError('Length of value must be 5 octets') @classmethod
for r in self.input_value: def validate_val(cls, val):
if r > 127: "allow_len checks the length, this in addition checks the value range"
raise ValueError('r values must be between 0 and 127') val = super().validate_val(val)
class MilenageXoringConstants(AlgoConfig, key='xoringConstants'): 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. """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 Provided as octet-string concatenation of all 5 constants. The default value by 3GPP is the concetenation
of: of:
@@ -558,16 +621,11 @@ class MilenageXoringConstants(AlgoConfig, key='xoringConstants'):
00000000000000000000000000000004 00000000000000000000000000000004
00000000000000000000000000000008 00000000000000000000000000000008
""" """
def validate(self): algo_config_key = 'xoringConstants'
super().validate() allow_len = 80 # length in bytes (from BinaryParam)
if len(self.input_value) != 80:
raise ValueError('Length of value must be 80 octets') class TuakNumberOfKeccak(IntegerParam, AlgoConfig):
class TuakNumberOfKeccak(AlgoConfig, key='numberOfKeccak'): """Number of iterations of Keccak-f[1600] permutation as recomended by Section 7.2 of 3GPP TS 35.231"""
"""Number of iterations of Keccak-f[1600] permutation as recomended by Section 7.2 of 3GPP TS 35.231. algo_config_key = 'numberOfKeccak'
The default value by 3GPP is 1.""" min_val = 1
def validate(self): max_val = 255
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