diff --git a/contrib/saip-tool.py b/contrib/saip-tool.py index 68072fa3..6f2a2a4c 100755 --- a/contrib/saip-tool.py +++ b/contrib/saip-tool.py @@ -19,11 +19,13 @@ import os import sys import argparse import logging +import zipfile from pathlib import Path from typing import List from pySim.esim.saip import * from pySim.esim.saip.validation import CheckBasicStructure +from pySim import javacard from pySim.utils import h2b, b2h, swap_nibbles from pySim.pprint import HexBytesPrettyPrinter @@ -56,6 +58,10 @@ parser_rn.add_argument('--naa-type', required=True, choices=NAAs.keys(), help='N parser_info = subparsers.add_parser('info', help='Display information about the profile') +parser_eapp = subparsers.add_parser('extract-apps', help='Extract applications as loadblock file') +parser_eapp.add_argument('--output-dir', default='.', help='Output directory (where to store files)') +parser_eapp.add_argument('--format', default='cap', choices=['ijc', 'cap'], help='Data format of output files') + def do_split(pes: ProfileElementSequence, opts): i = 0 for pe in pes.pe_list: @@ -176,6 +182,21 @@ def do_info(pes: ProfileElementSequence, opts): print("\tInstance AID: %s" % b2h(inst['instanceAID'])) pass +def do_extract_apps(pes:ProfileElementSequence, opts): + apps = pes.pe_by_type.get('application', []) + for app_pe in apps: + package_aid = b2h(app_pe.decoded['loadBlock']['loadPackageAID']) + + fname = os.path.join(opts.output_dir, '%s-%s.%s' % (pes.iccid, package_aid, opts.format)) + load_block_obj = app_pe.decoded['loadBlock']['loadBlockObject'] + print("Writing Load Package AID: %s to file %s" % (package_aid, fname)) + if opts.format == 'ijc': + with open(fname, 'wb') as f: + f.write(load_block_obj) + else: + with io.BytesIO(load_block_obj) as f, zipfile.ZipFile(fname, 'w') as z: + javacard.ijc_to_cap(f, z, package_aid) + if __name__ == '__main__': opts = parser.parse_args() @@ -197,3 +218,5 @@ if __name__ == '__main__': do_remove_naa(pes, opts) elif opts.command == 'info': do_info(pes, opts) + elif opts.command == 'extract-apps': + do_extract_apps(pes, opts) diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py index 278b5cf4..e690e6c8 100644 --- a/pySim/esim/saip/__init__.py +++ b/pySim/esim/saip/__init__.py @@ -617,7 +617,19 @@ class ProfileElementSequence: # TODO: remove any records related to the ADFs from EF.DIR def __repr__(self) -> str: - return "PESequence(%s)" % ', '.join([str(x) for x in self.pe_list]) + return "PESequence(%s: %s)" % (self.iccid, ', '.join([str(x) for x in self.pe_list])) def __iter__(self) -> str: yield from self.pe_list + + @property + def iccid(self) -> Optional[str]: + """The ICCID of the profile.""" + if not 'header' in self.pe_by_type: + return None + if len(self.pe_by_type['header']) < 1: + return None + pe_hdr_dec = self.pe_by_type['header'][0].decoded + if not 'iccid' in pe_hdr_dec: + return None + return b2h(pe_hdr_dec['iccid']) diff --git a/pySim/javacard.py b/pySim/javacard.py new file mode 100644 index 00000000..46f6ec02 --- /dev/null +++ b/pySim/javacard.py @@ -0,0 +1,19 @@ +# JavaCard related utilities + +import zipfile +import struct +import sys +import io + +def ijc_to_cap(in_file: io.IOBase, out_zip: zipfile.ZipFile, p : str = "foo"): + """Convert an ICJ (Interoperable Java Card) file [back] to a CAP file.""" + TAGS = ["Header", "Directory", "Applet", "Import", "ConstantPool", "Class", "Method", "StaticField", "RefLocation", "Export", "Descriptor", "Debug"] + b = in_file.read() + while len(b): + tag, size = struct.unpack('!BH', b[0:3]) + out_zip.writestr(p+"/javacard/"+TAGS[tag-1]+".cap", b[0:3+size]) + b = b[3+size:] + +# example usage: +# with io.open(sys.argv[1],"rb") as f, zipfile.ZipFile(sys.argv[2], "wb") as z: +# ijc_to_cap(f, z)