From b6532b56d2e78be6ac98977b82d63c65879abe34 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Mon, 10 Jun 2024 14:44:28 +0200 Subject: [PATCH] saip-tool: Add 'extract-apps' to dump all applications from eSIM profile This new action can be used to dump all java applications as either raw IJC file or converted to CAP format (the usual format generated by JavaCard toolchains). Change-Id: I51cffa5ba3ddbea491341d678ec9249d7cf470a5 --- contrib/saip-tool.py | 23 +++++++++++++++++++++++ pySim/esim/saip/__init__.py | 14 +++++++++++++- pySim/javacard.py | 19 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 pySim/javacard.py 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)