laforge has uploaded this change for review. (
https://gerrit.osmocom.org/c/pysim/+/36014?usp=email )
Change subject: saip.personalization: include encode/decode of value; add validation
method
......................................................................
saip.personalization: include encode/decode of value; add validation method
Change-Id: Ia9fa39c25817448afb191061acd4be894300eeef
---
M pySim/esim/saip/personalization.py
1 file changed, 81 insertions(+), 13 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/14/36014/1
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index 5adaf2e..fe97e59 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -18,6 +18,7 @@
import abc
from typing import List, Tuple
+from pySim.utils import enc_iccid, enc_imsi, h2b, rpad, sanitize_iccid
from pySim.esim.saip import ProfileElement, ProfileElementSequence
def remove_unwanted_tuples_from_list(l: List[Tuple], unwanted_keys: List[str]) ->
List[Tuple]:
@@ -47,26 +48,56 @@
def __init__(self, value):
self.value = value
+ def validate(self):
+ """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."""
+ pass
+
@abc.abstractmethod
def apply(self, pes: ProfileElementSequence):
pass
class Iccid(ConfigurableParameter):
- """Configurable ICCID. Expects the value to be in EF.ICCID
format."""
+ """Configurable ICCID. Expects the value to be a string of decimal
digits.
+ If the string of digits is only 18 digits long, a Luhn check digit will be
added."""
name = 'iccid'
+
+ def validate(self):
+ # convert to string as it migt be an integer
+ iccid_str = str(self.value)
+ if len(iccid_str) < 18 or len(iccid_str) > 20:
+ raise ValueError('ICCID must be 18, 19 or 20 digits long')
+ if not iccid_str.isdecimal():
+ raise ValueError('ICCID must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
- # patch the header; FIXME: swap nibbles!
- pes.get_pe_for_type('header').decoded['iccid'] = self.value
+ iccid_str = sanitize_iccid(self.value)
+ # patch the header
+ pes.get_pe_for_type('header').decoded['iccid'] = iccid_str
# patch MF/EF.ICCID
-
file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'],
bytes(self.value))
+
file_replace_content(pes.get_pe_for_type('mf').decoded['ef-iccid'],
h2b(enc_iccid(iccid_str)))
class Imsi(ConfigurableParameter):
- """Configurable IMSI. Expects value to be n EF.IMSI
format."""
+ """Configurable IMSI. Expects value to be a string of digits.
Automatically sets the ACC to
+ the last digit of the IMSI."""
name = 'imsi'
+
+ def validate(self):
+ # convert to string as it migt be an integer
+ imsi_str = str(self.value)
+ if len(imsi_str < 6) or len(imsi_str) > 15:
+ raise ValueError('IMSI must be 6..15 digits long')
+ if not imsi_str.isdecimal():
+ raise ValueError('IMSI must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
+ imsi_str = str(self.value)
+ # we always use the least significant byte of the IMSI as ACC
+ acc = (1 << int(imsi_str[-1]))
# patch ADF.USIM/EF.IMSI
- for pe in pes.get_pes_by_type('usim'):
- file_replace_content(pe.decoded['ef-imsi'], self.value)
+ for pe in pes.get_pes_for_type('usim'):
+ file_replace_content(pe.decoded['ef-imsi'], h2b(enc_imsi(imsi_str)))
+ file_replace_content(pe.decoded['ef-acc'], acc.to_bytes(2,
'big'))
# TODO: DF.GSM_ACCESS if not linked?
def obtain_singleton_pe_from_pelist(l: List[ProfileElement], wanted_type: str) ->
ProfileElement:
@@ -81,12 +112,19 @@
class Puk(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PUK (Pin Unblock Code). String ASCII-encoded
digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ puk_str = str(self.value)
+ # FIXME: valid length?
+ if not puk_str.isdecimal():
+ raise ValueError('PUK must only contain decimal digits')
+
def apply(self, pes: ProfileElementSequence):
mf_pes = pes.pes_by_naa['mf'][0]
pukCodes = obtain_singleton_pe_from_pelist(mf_pes, 'pukCodes')
for pukCode in pukCodes.decoded['pukCodes']:
if pukCode['keyReference'] == self.keyReference:
- pukCode['pukValue'] = self.value
+ pukCode['pukValue'] = h2b(self.value)
return
raise ValueError('cannot find pukCode')
class Puk1(Puk, keyReference=0x01):
@@ -97,29 +135,46 @@
class Pin(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PIN (Personal Identification Number). String of
digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ pin_str = str(self.value)
+ if len(pin_str) < 4 or len(pin_str) > 8:
+ raise ValueError('PIN mus be 4..8 digits long')
+ if not pin_str.isdecimal():
+ raise ValueError('PIN must only contain decimal digits')
def apply(self, pes: ProfileElementSequence):
+ pin = ''.join(['%02x' % (ord(x)) for x in self.value])
+ padded_pin = rpad(pin, 16)
mf_pes = pes.pes_by_naa['mf'][0]
pinCodes = obtain_first_pe_from_pelist(mf_pes, 'pinCodes')
if pinCodes.decoded['pinCodes'][0] != 'pinconfig':
return
for pinCode in pinCodes.decoded['pinCodes'][1]:
if pinCode['keyReference'] == self.keyReference:
- pinCode['pinValue'] = self.value
+ pinCode['pinValue'] = h2b(padded_pin)
return
raise ValueError('cannot find pinCode')
class AppPin(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable PIN (Personal Identification Number). String of
digits."""
keyReference = None
+ def validate(self):
+ # convert to string as it migt be an integer
+ pin_str = str(self.value)
+ if len(pin_str) < 4 or len(pin_str) > 8:
+ raise ValueError('PIN mus be 4..8 digits long')
+ if not pin_str.isdecimal():
+ raise ValueError('PIN must only contain decimal digits')
def _apply_one(self, pe: ProfileElement):
+ pin = ''.join(['%02x' % (ord(x)) for x in self.value])
+ padded_pin = rpad(pin, 16)
pinCodes = obtain_first_pe_from_pelist(pe, 'pinCodes')
if pinCodes.decoded['pinCodes'][0] != 'pinconfig':
return
for pinCode in pinCodes.decoded['pinCodes'][1]:
if pinCode['keyReference'] == self.keyReference:
- pinCode['pinValue'] = self.value
+ pinCode['pinValue'] = h2b(padded_pin)
return
raise ValueError('cannot find pinCode')
-
def apply(self, pes: ProfileElementSequence):
for naa in pes.pes_by_naa:
if naa not in
['usim','isim','csim','telecom']:
@@ -140,6 +195,9 @@
class AlgoConfig(ConfigurableParameter, metaclass=ClassVarMeta):
"""Configurable Algorithm parameter. bytes."""
key = None
+ def validate(self):
+ if not isinstance(self.value, (BytesIO, bytes, bytearray)):
+ raise ValueError('Value must be of bytes-like type')
def apply(self, pes: ProfileElementSequence):
for pe in pes.get_pes_for_type('akaParameter'):
algoConfiguration = pe.decoded['algoConfiguration']
@@ -152,5 +210,6 @@
class Opc(AlgoConfig, key='opc'):
pass
class AlgorithmID(AlgoConfig, key='algorithmID'):
- pass
-
+ def validate(self):
+ if self.value not in [1, 2, 3]:
+ raise ValueError('Invalid algorithmID %s' % (self.value))
--
To view, visit
https://gerrit.osmocom.org/c/pysim/+/36014?usp=email
To unsubscribe, or for help writing mail filters, visit
https://gerrit.osmocom.org/settings
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ia9fa39c25817448afb191061acd4be894300eeef
Gerrit-Change-Number: 36014
Gerrit-PatchSet: 1
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-MessageType: newchange