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
This commit is contained in:
Harald Welte
2024-06-10 14:44:28 +02:00
parent 3d70f659f3
commit b6532b56d2
3 changed files with 55 additions and 1 deletions

View File

@@ -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)

View File

@@ -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'])

19
pySim/javacard.py Normal file
View File

@@ -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)