laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/35749?usp=email )
Change subject: pySim.esim: Add class for parsing/encoding eSIM activation codes ......................................................................
pySim.esim: Add class for parsing/encoding eSIM activation codes
Change-Id: I2256722c04b56e8d9c16a65e3cd94f6a46f4ed85 --- M pySim/esim/__init__.py M tests/test_esim.py 2 files changed, 99 insertions(+), 0 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/pySim/esim/__init__.py b/pySim/esim/__init__.py index 4d6c609..dfacb83 100644 --- a/pySim/esim/__init__.py +++ b/pySim/esim/__init__.py @@ -1,4 +1,5 @@ import sys +from typing import Optional from importlib import resources
import asn1tools @@ -14,3 +15,79 @@ #else: #print(resources.read_text(__name__, 'asn1/rsp.asn')) return asn1tools.compile_string(asn_txt, codec='der') + + +# SGP.22 section 4.1 Activation Code +class ActivationCode: + def __init__(self, hostname:str, token:str, oid: Optional[str] = None, cc_required: Optional[bool] = False): + if '$' in hostname: + raise ValueError('$ sign not permitted in hostname') + self.hostname = hostname + if '$' in token: + raise ValueError('$ sign not permitted in token') + self.token = token + # TODO: validate OID + self.oid = oid + self.cc_required = cc_required + # only format 1 is specified and supported here + self.format = 1 + + @staticmethod + def decode_str(ac: str) -> dict: + if ac[0] != '1': + raise ValueError("Unsupported AC_Format '%s'!" % ac[0]) + ac_elements = ac.split('$') + d = { + 'oid': None, + 'cc_required': False, + } + d['format'] = ac_elements.pop(0) + d['hostname'] = ac_elements.pop(0) + d['token'] = ac_elements.pop(0) + if len(ac_elements): + oid = ac_elements.pop(0) + if oid != '': + d['oid'] = oid + if len(ac_elements): + ccr = ac_elements.pop(0) + if ccr == '1': + d['cc_required'] = True + return d + + @classmethod + def from_string(cls, ac: str) -> 'ActivationCode': + """Create new instance from SGP.22 section 4.1 string representation.""" + d = cls.decode_str(ac) + return cls(d['hostname'], d['token'], d['oid'], d['cc_required']) + + def to_string(self, for_qrcode:bool = False) -> str: + """Convert from internal representation to SGP.22 section 4.1 string representation.""" + if for_qrcode: + ret = 'LPA:' + else: + ret = '' + ret += '%d$%s$%s' % (self.format, self.hostname, self.token) + if self.oid: + ret += '$%s' % (self.oid) + elif self.cc_required: + ret += '$' + if self.cc_required: + ret += '$1' + return ret + + def __str__(self): + return self.to_string() + + def to_qrcode(self): + """Encode internal representation to QR code.""" + import qrcode + qr = qrcode.QRCode() + qr.add_data(self.to_string(for_qrcode=True)) + return qr.make_image() + + def __repr__(self): + return "ActivationCode(format=%u, hostname='%s', token='%s', oid=%s, cc_required=%s)" % (self.format, + self.hostname, + self.token, + self.oid, + self.cc_required) diff --git a/tests/test_esim.py b/tests/test_esim.py index d4b494e..81926e5 100755 --- a/tests/test_esim.py +++ b/tests/test_esim.py @@ -22,9 +22,22 @@ from pySim.utils import b2h, h2b from pySim.esim.bsp import * import pySim.esim.rsp as rsp +from pySim.esim import ActivationCode
from cryptography.hazmat.primitives.asymmetric import ec
+class TestActivationCode(unittest.TestCase): + def test_de_encode(self): + STRS = ['1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815', + '1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$$1', + '1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$1.3.6.1.4.1.31746$1', + '1$SMDP.GSMA.COM$04386-AGYFT-A74Y8-3F815$1.3.6.1.4.1.31746', + '1$SMDP.GSMA.COM$$1.3.6.1.4.1.31746'] + for s in STRS: + ac = ActivationCode.from_string(s) + self.assertEqual(s, ac.to_string()) + + class TestECKA(unittest.TestCase): def test_mode51(self): curve = ec.SECP256R1()