From 3d70f659f3813f8fc882d4563eb5bcfc01187e10 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Mon, 10 Jun 2024 13:33:20 +0200 Subject: [PATCH] saip-tool: Add new 'info' action to print general information It will print something like this: SAIP Profile Version: 2.1 Profile Type: 'GSMA Generic eUICC Test Profile' ICCID: 8949449999999990023f Mandatory Services: usim, isim, csim, javacard, usim-test-algorithm NAAs: mf[1], usim[1], csim[1], isim[1] NAA mf NAA usim (a0000000871002ff49ff0589) IMSI: 001010123456063 NAA csim NAA isim (a0000000871004ff49ff0589) Number of applications: 0 Change-Id: I107d457c3313a766229b569453c18a8d69134bec --- contrib/saip-tool.py | 42 +++++++++++++++++++++++++++++++++++++ pySim/esim/saip/__init__.py | 27 +++++++++++++++++++++--- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/contrib/saip-tool.py b/contrib/saip-tool.py index 9bbaddf9..68072fa3 100755 --- a/contrib/saip-tool.py +++ b/contrib/saip-tool.py @@ -54,6 +54,7 @@ parser_rn.add_argument('--output-file', required=True, help='Output file name') parser_rn.add_argument('--naa-type', required=True, choices=NAAs.keys(), help='Network Access Application type to remove') # TODO: add an --naa-index or the like, so only one given instance can be removed +parser_info = subparsers.add_parser('info', help='Display information about the profile') def do_split(pes: ProfileElementSequence, opts): i = 0 @@ -136,6 +137,45 @@ def do_remove_naa(pes: ProfileElementSequence, opts): with open(opts.output_file, 'wb') as f: f.write(pes.to_der()) +def do_info(pes: ProfileElementSequence, opts): + def get_naa_count(pes: ProfileElementSequence) -> dict: + """return a dict with naa-type (usim, isim) as key and the count of NAA instances as value.""" + ret = {} + for naa_type in pes.pes_by_naa: + ret[naa_type] = len(pes.pes_by_naa[naa_type]) + return ret + + pe_hdr_dec = pes.pe_by_type['header'][0].decoded + print() + print("SAIP Profile Version: %u.%u" % (pe_hdr_dec['major-version'], pe_hdr_dec['minor-version'])) + print("Profile Type: '%s'" % pe_hdr_dec['profileType']) + print("ICCID: %s" % b2h(pe_hdr_dec['iccid'])) + print("Mandatory Services: %s" % ', '.join(pe_hdr_dec['eUICC-Mandatory-services'].keys())) + print() + naa_strs = ["%s[%u]" % (k, v) for k, v in get_naa_count(pes).items()] + print("NAAs: %s" % ', '.join(naa_strs)) + for naa_type in pes.pes_by_naa: + for naa_inst in pes.pes_by_naa[naa_type]: + first_pe = naa_inst[0] + adf_name = '' + if hasattr(first_pe, 'adf_name'): + adf_name = '(' + first_pe.adf_name + ')' + print("NAA %s %s" % (first_pe.type, adf_name)) + if hasattr(first_pe, 'imsi'): + print("\tIMSI: %s" % first_pe.imsi) + + # applications + print() + apps = pes.pe_by_type.get('application', []) + print("Number of applications: %u" % len(apps)) + for app_pe in apps: + print("App Load Package AID: %s" % b2h(app_pe.decoded['loadBlock']['loadPackageAID'])) + print("\tMandated: %s" % ('mandated' in app_pe.decoded['app-Header'])) + print("\tLoad Block Size: %s" % len(app_pe.decoded['loadBlock']['loadBlockObject'])) + for inst in app_pe.decoded.get('instanceList', []): + print("\tInstance AID: %s" % b2h(inst['instanceAID'])) + pass + if __name__ == '__main__': opts = parser.parse_args() @@ -155,3 +195,5 @@ if __name__ == '__main__': do_remove_pe(pes, opts) elif opts.command == 'remove-naa': do_remove_naa(pes, opts) + elif opts.command == 'info': + do_info(pes, opts) diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py index ee2538c8..278b5cf4 100644 --- a/pySim/esim/saip/__init__.py +++ b/pySim/esim/saip/__init__.py @@ -22,7 +22,7 @@ from collections import OrderedDict import asn1tools -from pySim.utils import bertlv_parse_tag, bertlv_parse_len, b2h, h2b +from pySim.utils import bertlv_parse_tag, bertlv_parse_len, b2h, h2b, dec_imsi from pySim.ts_102_221 import FileDescriptor from pySim.construct import build_construct from pySim.esim import compile_asn1_subdir @@ -263,10 +263,15 @@ class ProfileElement: @classmethod def from_der(cls, der: bytes) -> 'ProfileElement': + class4petype = { + 'securityDomain': ProfileElementSD, + 'usim': ProfileElementUSIM, + 'isim': ProfileElementISIM, + } """Construct an instance from given raw, DER encoded bytes.""" pe_type, decoded = asn1.decode('ProfileElement', der) - if pe_type == 'securityDomain': - inst = ProfileElementSD(decoded) + if pe_type in class4petype: + inst = class4petype[pe_type](decoded) else: inst = ProfileElement(decoded) inst.type = pe_type @@ -437,6 +442,22 @@ class ProfileElementSSD(ProfileElementSD): 'uiccToolkitApplicationSpecificParametersField': h2b('01000001000000020112036C756500'), } +class ProfileElementUSIM(ProfileElement): + type = 'usim' + @property + def adf_name(self) -> str: + return b2h(self.decoded['adf-usim'][0][1]['dfName']) + @property + def imsi(self) -> Optional[str]: + f = File('ef-imsi', self.decoded['ef-imsi']) + return dec_imsi(b2h(f.stream.getvalue())) + +class ProfileElementISIM(ProfileElement): + type = 'isim' + @property + def adf_name(self) -> str: + return b2h(self.decoded['adf-isim'][0][1]['dfName']) + def bertlv_first_segment(binary: bytes) -> Tuple[bytes, bytes]: """obtain the first segment of a binary concatenation of BER-TLV objects. Returns: tuple of first TLV and remainder."""