mirror of
https://gitea.osmocom.org/sim-card/pysim.git
synced 2026-03-26 15:28:35 +03:00
saip.personalization: differentiate input_value from value
When personalizing e.g. the ICCID, the input_value is the raw incrementing counter. From that, we calculate the Luhn check digit, and that "output" value is what we'll put in to the EF.ICCID specific encoder. However, we also store that output value in the instance in order to generate the output CSV file containig the card-specific personalization data. Change-Id: Idfcd26c8ca9d73a9c2955f7c97e711dd59a27c4e
This commit is contained in:
@@ -46,13 +46,15 @@ class ClassVarMeta(abc.ABCMeta):
|
|||||||
class ConfigurableParameter(abc.ABC, metaclass=ClassVarMeta):
|
class ConfigurableParameter(abc.ABC, metaclass=ClassVarMeta):
|
||||||
"""Base class representing a part of the eSIM profile that is configurable during the
|
"""Base class representing a part of the eSIM profile that is configurable during the
|
||||||
personalization process (with dynamic data from elsewhere)."""
|
personalization process (with dynamic data from elsewhere)."""
|
||||||
def __init__(self, value):
|
def __init__(self, input_value):
|
||||||
self.value = value
|
self.input_value = input_value # the raw input value as given by caller
|
||||||
|
self.value = None # the processed input value (e.g. with check digit) as produced by validate()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""Optional validation method. Can be used by derived classes to perform validation
|
"""Optional validation method. Can be used by derived classes to perform validation
|
||||||
of the input value (self.value). Will raise an exception if validation fails."""
|
of the input value (self.value). Will raise an exception if validation fails."""
|
||||||
pass
|
# default implementation: simply copy input_value over to value
|
||||||
|
self.value = self.input_value
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def apply(self, pes: ProfileElementSequence):
|
def apply(self, pes: ProfileElementSequence):
|
||||||
@@ -65,18 +67,18 @@ class Iccid(ConfigurableParameter):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
# convert to string as it migt be an integer
|
# convert to string as it migt be an integer
|
||||||
iccid_str = str(self.value)
|
iccid_str = str(self.input_value)
|
||||||
if len(iccid_str) < 18 or len(iccid_str) > 20:
|
if len(iccid_str) < 18 or len(iccid_str) > 20:
|
||||||
raise ValueError('ICCID must be 18, 19 or 20 digits long')
|
raise ValueError('ICCID must be 18, 19 or 20 digits long')
|
||||||
if not iccid_str.isdecimal():
|
if not iccid_str.isdecimal():
|
||||||
raise ValueError('ICCID must only contain decimal digits')
|
raise ValueError('ICCID must only contain decimal digits')
|
||||||
|
self.value = sanitize_iccid(iccid_str)
|
||||||
|
|
||||||
def apply(self, pes: ProfileElementSequence):
|
def apply(self, pes: ProfileElementSequence):
|
||||||
iccid_str = sanitize_iccid(self.value)
|
|
||||||
# patch the header
|
# patch the header
|
||||||
pes.get_pe_for_type('header').decoded['iccid'] = iccid_str
|
pes.get_pe_for_type('header').decoded['iccid'] = self.value
|
||||||
# patch MF/EF.ICCID
|
# patch MF/EF.ICCID
|
||||||
file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'], h2b(enc_iccid(iccid_str)))
|
file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'], h2b(enc_iccid(self.value)))
|
||||||
|
|
||||||
class Imsi(ConfigurableParameter):
|
class Imsi(ConfigurableParameter):
|
||||||
"""Configurable IMSI. Expects value to be a string of digits. Automatically sets the ACC to
|
"""Configurable IMSI. Expects value to be a string of digits. Automatically sets the ACC to
|
||||||
@@ -85,14 +87,15 @@ class Imsi(ConfigurableParameter):
|
|||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
# convert to string as it migt be an integer
|
# convert to string as it migt be an integer
|
||||||
imsi_str = str(self.value)
|
imsi_str = str(self.input_value)
|
||||||
if len(imsi_str) < 6 or len(imsi_str) > 15:
|
if len(imsi_str) < 6 or len(imsi_str) > 15:
|
||||||
raise ValueError('IMSI must be 6..15 digits long')
|
raise ValueError('IMSI must be 6..15 digits long')
|
||||||
if not imsi_str.isdecimal():
|
if not imsi_str.isdecimal():
|
||||||
raise ValueError('IMSI must only contain decimal digits')
|
raise ValueError('IMSI must only contain decimal digits')
|
||||||
|
self.value = imsi_str
|
||||||
|
|
||||||
def apply(self, pes: ProfileElementSequence):
|
def apply(self, pes: ProfileElementSequence):
|
||||||
imsi_str = str(self.value)
|
imsi_str = self.value
|
||||||
# we always use the least significant byte of the IMSI as ACC
|
# we always use the least significant byte of the IMSI as ACC
|
||||||
acc = (1 << int(imsi_str[-1]))
|
acc = (1 << int(imsi_str[-1]))
|
||||||
# patch ADF.USIM/EF.IMSI
|
# patch ADF.USIM/EF.IMSI
|
||||||
@@ -112,11 +115,12 @@ class SdKey(ConfigurableParameter, metaclass=ClassVarMeta):
|
|||||||
permitted_len = None
|
permitted_len = None
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not isinstance(self.value, (io.BytesIO, bytes, bytearray)):
|
if not isinstance(self.input_value, (io.BytesIO, bytes, bytearray)):
|
||||||
raise ValueError('Value must be of bytes-like type')
|
raise ValueError('Value must be of bytes-like type')
|
||||||
if self.permitted_len:
|
if self.permitted_len:
|
||||||
if len(self.value) not in self.permitted_len:
|
if len(self.input_value) not in self.permitted_len:
|
||||||
raise ValueError('Value length must be %s' % self.permitted_len)
|
raise ValueError('Value length must be %s' % self.permitted_len)
|
||||||
|
self.value = self.input_value
|
||||||
|
|
||||||
def _apply_sd(self, pe: ProfileElement):
|
def _apply_sd(self, pe: ProfileElement):
|
||||||
assert pe.type == 'securityDomain'
|
assert pe.type == 'securityDomain'
|
||||||
@@ -208,8 +212,10 @@ class Puk(ConfigurableParameter, metaclass=ClassVarMeta):
|
|||||||
"""Configurable PUK (Pin Unblock Code). String ASCII-encoded digits."""
|
"""Configurable PUK (Pin Unblock Code). String ASCII-encoded digits."""
|
||||||
keyReference = None
|
keyReference = None
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if isinstance(self.value, int):
|
if isinstance(self.input_value, int):
|
||||||
self.value = '%08d' % self.value
|
self.value = '%08d' % self.input_value
|
||||||
|
else:
|
||||||
|
self.value = self.input_value
|
||||||
# FIXME: valid length?
|
# FIXME: valid length?
|
||||||
if not self.value.isdecimal():
|
if not self.value.isdecimal():
|
||||||
raise ValueError('PUK must only contain decimal digits')
|
raise ValueError('PUK must only contain decimal digits')
|
||||||
@@ -233,8 +239,10 @@ class Pin(ConfigurableParameter, metaclass=ClassVarMeta):
|
|||||||
"""Configurable PIN (Personal Identification Number). String of digits."""
|
"""Configurable PIN (Personal Identification Number). String of digits."""
|
||||||
keyReference = None
|
keyReference = None
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if isinstance(self.value, int):
|
if isinstance(self.input_value, int):
|
||||||
self.value = '%04d' % self.value
|
self.value = '%04d' % self.input_value
|
||||||
|
else:
|
||||||
|
self.value = self.input_value
|
||||||
if len(self.value) < 4 or len(self.value) > 8:
|
if len(self.value) < 4 or len(self.value) > 8:
|
||||||
raise ValueError('PIN mus be 4..8 digits long')
|
raise ValueError('PIN mus be 4..8 digits long')
|
||||||
if not self.value.isdecimal():
|
if not self.value.isdecimal():
|
||||||
@@ -255,8 +263,10 @@ class AppPin(ConfigurableParameter, metaclass=ClassVarMeta):
|
|||||||
"""Configurable PIN (Personal Identification Number). String of digits."""
|
"""Configurable PIN (Personal Identification Number). String of digits."""
|
||||||
keyReference = None
|
keyReference = None
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if isinstance(self.value, int):
|
if isinstance(self.input_value, int):
|
||||||
self.value = '%04d' % self.value
|
self.value = '%04d' % self.input_value
|
||||||
|
else:
|
||||||
|
self.value = self.input_value
|
||||||
if len(self.value) < 4 or len(self.value) > 8:
|
if len(self.value) < 4 or len(self.value) > 8:
|
||||||
raise ValueError('PIN mus be 4..8 digits long')
|
raise ValueError('PIN mus be 4..8 digits long')
|
||||||
if not self.value.isdecimal():
|
if not self.value.isdecimal():
|
||||||
@@ -293,8 +303,9 @@ class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
|
|||||||
"""Configurable Algorithm parameter. bytes."""
|
"""Configurable Algorithm parameter. bytes."""
|
||||||
key = None
|
key = None
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if not isinstance(self.value, (io.BytesIO, bytes, bytearray)):
|
if not isinstance(self.input_value, (io.BytesIO, bytes, bytearray)):
|
||||||
raise ValueError('Value must be of bytes-like type')
|
raise ValueError('Value must be of bytes-like type')
|
||||||
|
self.value = self.input_value
|
||||||
def apply(self, pes: ProfileElementSequence):
|
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']
|
||||||
@@ -308,5 +319,6 @@ class Opc(AlgoConfig, key='opc'):
|
|||||||
pass
|
pass
|
||||||
class AlgorithmID(AlgoConfig, key='algorithmID'):
|
class AlgorithmID(AlgoConfig, key='algorithmID'):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.value not in [1, 2, 3]:
|
if self.input_value not in [1, 2, 3]:
|
||||||
raise ValueError('Invalid algorithmID %s' % (self.value))
|
raise ValueError('Invalid algorithmID %s' % (self.input_value))
|
||||||
|
self.value = self.input_value
|
||||||
|
|||||||
Reference in New Issue
Block a user