laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/39860?usp=email )
Change subject: saip-tool: add features to add, remove and inspect application PEs ......................................................................
saip-tool: add features to add, remove and inspect application PEs
The PE-Application object is used to provision JAVA-card applications into an eUICC during profile installation. Let's extend the SAIP-tool so that we are able to add, remove and inspect applications.
Change-Id: I41db96f2f0ccc29c1725a92215ce6b17d87b76ce --- M contrib/saip-tool.py A contrib/saip-tool_example_add-app.sh A contrib/saip-tool_example_extract-apps.sh A contrib/saip-tool_example_remove-app-inst.sh A contrib/saip-tool_example_remove-app.sh M docs/index.rst A docs/saip-tool.rst M pySim/esim/saip/__init__.py 8 files changed, 499 insertions(+), 1 deletion(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/contrib/saip-tool.py b/contrib/saip-tool.py index 4747ab9..927db1f 100755 --- a/contrib/saip-tool.py +++ b/contrib/saip-tool.py @@ -22,6 +22,7 @@ from pathlib import Path as PlPath from typing import List from osmocom.utils import h2b, b2h, swap_nibbles +from osmocom.construct import GreedyBytes, StripHeaderAdapter
from pySim.esim.saip import * from pySim.esim.saip.validation import CheckBasicStructure @@ -61,11 +62,45 @@ # 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') +parser_info.add_argument('--apps', action='store_true', help='List applications and their related instances')
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')
+parser_aapp = subparsers.add_parser('add-app', help='Add application to PE-Sequence') +parser_aapp.add_argument('--output-file', required=True, help='Output file name') +parser_aapp.add_argument('--applet-file', required=True, help='Applet file name') +parser_aapp.add_argument('--aid', required=True, help='Load package AID') +parser_aapp.add_argument('--sd-aid', default=None, help='Security Domain AID') +parser_aapp.add_argument('--non-volatile-code-limit', default=None, type=int, help='Non volatile code limit (C6)') +parser_aapp.add_argument('--volatile-data-limit', default=None, type=int, help='Volatile data limit (C7)') +parser_aapp.add_argument('--non-volatile-data-limit', default=None, type=int, help='Non volatile data limit (C8)') +parser_aapp.add_argument('--hash-value', default=None, help='Hash value') + +parser_rapp = subparsers.add_parser('remove-app', help='Remove application from PE-Sequence') +parser_rapp.add_argument('--output-file', required=True, help='Output file name') +parser_rapp.add_argument('--aid', required=True, help='Load package AID') + +parser_aappi = subparsers.add_parser('add-app-inst', help='Add application instance to Application PE') +parser_aappi.add_argument('--output-file', required=True, help='Output file name') +parser_aappi.add_argument('--aid', required=True, help='Load package AID') +parser_aappi.add_argument('--class-aid', required=True, help='Class AID') +parser_aappi.add_argument('--inst-aid', required=True, help='Instance AID (must match Load package AID)') +parser_aappi.add_argument('--app-privileges', default='000000', help='Application privileges') +parser_aappi.add_argument('--volatile-memory-quota', default=None, type=int, help='Volatile memory quota (C7)') +parser_aappi.add_argument('--non-volatile-memory-quota', default=None, type=int, help='Non volatile memory quota (C8)') +parser_aappi.add_argument('--app-spec-pars', default='00', help='Application specific parameters (C9)') +parser_aappi.add_argument('--uicc-toolkit-app-spec-pars', help='UICC toolkit application specific parameters field') +parser_aappi.add_argument('--uicc-access-app-spec-pars', help='UICC Access application specific parameters field') +parser_aappi.add_argument('--uicc-adm-access-app-spec-pars', help='UICC Administrative access application specific parameters field') +parser_aappi.add_argument('--process-data', default=[], action='append', help='Process personalization APDUs') + +parser_rappi = subparsers.add_parser('remove-app-inst', help='Remove application instance from Application PE') +parser_rappi.add_argument('--output-file', required=True, help='Output file name') +parser_rappi.add_argument('--aid', required=True, help='Load package AID') +parser_rappi.add_argument('--inst-aid', required=True, help='Instance AID') + parser_info = subparsers.add_parser('tree', help='Display the filesystem tree')
def write_pes(pes: ProfileElementSequence, output_file:str): @@ -162,6 +197,82 @@ pes.remove_naas_of_type(naa) write_pes(pes, opts.output_file)
+def info_apps(pes:ProfileElementSequence): + def show_member(dictionary:Optional[dict], member:str, indent:str="\t", mandatory:bool = False, limit:bool = False): + if dictionary is None: + return + value = dictionary.get(member, None) + if value is None and mandatory == True: + print("%s%s: (missing!)" % (indent, member)) + return + elif value is None: + return + + if limit and len(value) > 40: + print("%s%s: '%s...%s' (%u bytes)" % (indent, member, b2h(value[:20]), b2h(value[-20:]), len(value))) + else: + print("%s%s: '%s' (%u bytes)" % (indent, member, b2h(value), len(value))) + + apps = pes.pe_by_type.get('application', []) + if len(apps) == 0: + print("No Application PE present!") + return; + + for app_pe in enumerate(apps): + print("Application #%u:" % app_pe[0]) + print("\tloadBlock:") + load_block = app_pe[1].decoded['loadBlock'] + show_member(load_block, 'loadPackageAID', "\t\t", True) + show_member(load_block, 'securityDomainAID', "\t\t") + show_member(load_block, 'nonVolatileCodeLimitC6', "\t\t") + show_member(load_block, 'volatileDataLimitC7', "\t\t") + show_member(load_block, 'nonVolatileDataLimitC8', "\t\t") + show_member(load_block, 'hashValue', "\t\t") + show_member(load_block, 'loadBlockObject', "\t\t", True, True) + for inst in enumerate(app_pe[1].decoded.get('instanceList', [])): + print("\tinstanceList[%u]:" % inst[0]) + show_member(inst[1], 'applicationLoadPackageAID', "\t\t", True) + if inst[1].get('applicationLoadPackageAID', None) != load_block.get('loadPackageAID', None): + print("\t\t(applicationLoadPackageAID should be the same as loadPackageAID!)") + show_member(inst[1], 'classAID', "\t\t", True) + show_member(inst[1], 'instanceAID', "\t\t", True) + show_member(inst[1], 'extraditeSecurityDomainAID', "\t\t") + show_member(inst[1], 'applicationPrivileges', "\t\t", True) + show_member(inst[1], 'lifeCycleState', "\t\t", True) + show_member(inst[1], 'applicationSpecificParametersC9', "\t\t", True) + sys_specific_pars = inst[1].get('systemSpecificParameters', None) + if sys_specific_pars: + print("\t\tsystemSpecificParameters:") + show_member(sys_specific_pars, 'volatileMemoryQuotaC7', "\t\t\t") + show_member(sys_specific_pars, 'nonVolatileMemoryQuotaC8', "\t\t\t") + show_member(sys_specific_pars, 'globalServiceParameters', "\t\t\t") + show_member(sys_specific_pars, 'implicitSelectionParameter', "\t\t\t") + show_member(sys_specific_pars, 'volatileReservedMemory', "\t\t\t") + show_member(sys_specific_pars, 'nonVolatileReservedMemory', "\t\t\t") + show_member(sys_specific_pars, 'ts102226SIMFileAccessToolkitParameter', "\t\t\t") + additional_cl_pars = inst.get('ts102226AdditionalContactlessParameters', None) + if additional_cl_pars: + print("\t\t\tts102226AdditionalContactlessParameters:") + show_member(additional_cl_pars, 'protocolParameterData', "\t\t\t\t") + show_member(sys_specific_pars, 'userInteractionContactlessParameters', "\t\t\t") + show_member(sys_specific_pars, 'cumulativeGrantedVolatileMemory', "\t\t\t") + show_member(sys_specific_pars, 'cumulativeGrantedNonVolatileMemory', "\t\t\t") + app_pars = inst[1].get('applicationParameters', None) + if app_pars: + print("\t\tapplicationParameters:") + show_member(app_pars, 'uiccToolkitApplicationSpecificParametersField', "\t\t\t") + show_member(app_pars, 'uiccAccessApplicationSpecificParametersField', "\t\t\t") + show_member(app_pars, 'uiccAdministrativeAccessApplicationSpecificParametersField', "\t\t\t") + ctrl_ref_tp = inst[1].get('controlReferenceTemplate', None) + if ctrl_ref_tp: + print("\t\tcontrolReferenceTemplate:") + show_member(ctrl_ref_tp, 'applicationProviderIdentifier', "\t\t\t", True) + process_data = inst[1].get('processData', None) + if process_data: + print("\t\tprocessData:") + for proc in process_data: + print("\t\t\t" + b2h(proc)) + 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.""" @@ -170,6 +281,10 @@ ret[naa_type] = len(pes.pes_by_naa[naa_type]) return ret
+ if opts.apps: + info_apps(pes) + return; + 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'])) @@ -233,6 +348,76 @@ print("Writing Load Package AID: %s to file %s" % (package_aid, fname)) app_pe.to_file(fname)
+def do_add_app(pes:ProfileElementSequence, opts): + print("Applying applet file: '%s'..." % opts.applet_file) + app_pe = ProfileElementApplication.from_file(opts.applet_file, + opts.aid, + opts.sd_aid, + opts.non_volatile_code_limit, + opts.volatile_data_limit, + opts.non_volatile_data_limit, + opts.hash_value) + + security_domain = pes.pe_by_type.get('securityDomain', []) + if len(security_domain) == 0: + print("profile package does not contain a securityDomain, please add a securityDomain PE first!") + elif len(security_domain) > 1: + print("adding an application PE to profiles with multiple securityDomain is not supported yet!") + else: + pes.insert_after_pe(security_domain[0], app_pe) + print("application PE inserted into PE Sequence after securityDomain PE AID: %s" % + b2h(security_domain[0].decoded['instance']['instanceAID'])) + write_pes(pes, opts.output_file) + +def do_remove_app(pes:ProfileElementSequence, opts): + apps = pes.pe_by_type.get('application', []) + for app_pe in apps: + package_aid = b2h(app_pe.decoded['loadBlock']['loadPackageAID']) + if opts.aid == package_aid: + identification = app_pe.identification + opts_remove_pe = argparse.Namespace() + opts_remove_pe.identification = [app_pe.identification] + opts_remove_pe.type = [] + opts_remove_pe.output_file = opts.output_file + print("Found Load Package AID: %s, removing related PE (id=%u) from Sequence..." % + (package_aid, identification)) + do_remove_pe(pes, opts_remove_pe) + return + print("Load Package AID: %s not found in PE Sequence" % opts.aid) + +def do_add_app_inst(pes:ProfileElementSequence, opts): + apps = pes.pe_by_type.get('application', []) + for app_pe in apps: + package_aid = b2h(app_pe.decoded['loadBlock']['loadPackageAID']) + if opts.aid == package_aid: + print("Found Load Package AID: %s, adding new instance AID: %s to Application PE..." % + (opts.aid, opts.inst_aid)) + app_pe.add_instance(opts.aid, + opts.class_aid, + opts.inst_aid, + opts.app_privileges, + opts.app_spec_pars, + opts.uicc_toolkit_app_spec_pars, + opts.uicc_access_app_spec_pars, + opts.uicc_adm_access_app_spec_pars, + opts.volatile_memory_quota, + opts.non_volatile_memory_quota, + opts.process_data) + write_pes(pes, opts.output_file) + return + print("Load Package AID: %s not found in PE Sequence" % opts.aid) + +def do_remove_app_inst(pes:ProfileElementSequence, opts): + apps = pes.pe_by_type.get('application', []) + for app_pe in apps: + if opts.aid == b2h(app_pe.decoded['loadBlock']['loadPackageAID']): + print("Found Load Package AID: %s, removing instance AID: %s from Application PE..." % + (opts.aid, opts.inst_aid)) + app_pe.remove_instance(opts.inst_aid) + write_pes(pes, opts.output_file) + return + print("Load Package AID: %s not found in PE Sequence" % opts.aid) + def do_tree(pes:ProfileElementSequence, opts): pes.mf.print_tree()
@@ -265,5 +450,13 @@ do_info(pes, opts) elif opts.command == 'extract-apps': do_extract_apps(pes, opts) + elif opts.command == 'add-app': + do_add_app(pes, opts) + elif opts.command == 'remove-app': + do_remove_app(pes, opts) + elif opts.command == 'add-app-inst': + do_add_app_inst(pes, opts) + elif opts.command == 'remove-app-inst': + do_remove_app_inst(pes, opts) elif opts.command == 'tree': do_tree(pes, opts) diff --git a/contrib/saip-tool_example_add-app.sh b/contrib/saip-tool_example_add-app.sh new file mode 100755 index 0000000..5605a4b --- /dev/null +++ b/contrib/saip-tool_example_add-app.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# This is an example script to illustrate how to add JAVA card applets to an existing eUICC profile package. + +PYSIMPATH=../ +INPATH=../smdpp-data/upp/TS48V1-A-UNIQUE.der +OUTPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-hello.der +APPPATH=./HelloSTK_09122024.cap + +# Download example applet (see also https://gitea.osmocom.org/sim-card/hello-stk): +if ! [ -f $APPPATH ]; then + wget https://osmocom.org/attachments/download/8931/HelloSTK_09122024.cap +fi + +# Step #1: Create the application PE and load the ijc contents from the .cap file: +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $INPATH add-app \ + --output-file $OUTPATH --applet-file $APPPATH --aid 'D07002CA44' + +# Step #2: Create the application instance inside the application PE created in step #1: +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH add-app-inst --output-file $OUTPATH \ + --aid 'D07002CA44' \ + --class-aid 'D07002CA44900101' \ + --inst-aid 'D07002CA44900101' \ + --app-privileges '00' \ + --app-spec-pars '00' \ + --uicc-toolkit-app-spec-pars '01001505000000000000000000000000' + +# Display the contents of the resulting application PE: +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH info --apps + +# For an explaination of --uicc-toolkit-app-spec-pars, see: +# ETSI TS 102 226, section 8.2.1.3.2.2.1 diff --git a/contrib/saip-tool_example_extract-apps.sh b/contrib/saip-tool_example_extract-apps.sh new file mode 100755 index 0000000..e85a0ce --- /dev/null +++ b/contrib/saip-tool_example_extract-apps.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# This is an example script to illustrate how to extract JAVA card applets from an existing eUICC profile package. + +PYSIMPATH=../ +INPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-hello.der +OUTPATH=./ + +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $INPATH extract-apps --output-dir ./ --format ijc diff --git a/contrib/saip-tool_example_remove-app-inst.sh b/contrib/saip-tool_example_remove-app-inst.sh new file mode 100755 index 0000000..c0f6ba2 --- /dev/null +++ b/contrib/saip-tool_example_remove-app-inst.sh @@ -0,0 +1,14 @@ +#!/bin/bash +# This is an example script to illustrate how to remove a JAVA card applet instance from an application PE inside an +# existing eUICC profile package. + +PYSIMPATH=../ +INPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-hello.der +OUTPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-hello-no-inst.der + +# Remove application PE entirely +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $INPATH remove-app-inst \ + --output-file $OUTPATH --aid 'd07002ca44' --inst-aid 'd07002ca44900101' + +# Display the contents of the resulting application PE: +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH info --apps diff --git a/contrib/saip-tool_example_remove-app.sh b/contrib/saip-tool_example_remove-app.sh new file mode 100755 index 0000000..7d36ff8 --- /dev/null +++ b/contrib/saip-tool_example_remove-app.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# This is an example script to illustrate how to remove a JAVA card applet from an existing eUICC profile package. + +PYSIMPATH=../ +INPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-hello.der +OUTPATH=../smdpp-data/upp/TS48V1-A-UNIQUE-no-hello.der + +# Remove application PE entirely +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $INPATH remove-app \ + --output-file $OUTPATH --aid 'D07002CA44' + +# Display the contents of the resulting application PE: +PYTHONPATH=$PYSIMPATH python3 $PYSIMPATH/contrib/saip-tool.py $OUTPATH info --apps diff --git a/docs/index.rst b/docs/index.rst index be48ac8..3c6d5d7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,6 +46,7 @@ osmo-smdpp sim-rest suci-keytool + saip-tool
Indices and tables diff --git a/docs/saip-tool.rst b/docs/saip-tool.rst new file mode 100644 index 0000000..8aa8cd7 --- /dev/null +++ b/docs/saip-tool.rst @@ -0,0 +1,132 @@ +saip-tool +========= + +eSIM profiles are stored as a sequence of profile element (PE) objects in an ASN.1 DER encoded binary file. To inspect, +verify or make changes to those files, the `saip-tool.py` utility can be used. + +NOTE: The file format, eSIM SAIP (SimAlliance Interoperable Profile) is specified in `TCA eUICC Profile Package: +Interoperable Format Technical Specification` + + +Profile Package Examples +~~~~~~~~~~~~~~~~~~~~~~~~ + +pySim ships with a set of TS48 profile package examples. Those examples can be found in `pysim/smdpp-data/upp`. The +files can be used as input for `saip-tool.py`. (see also GSMA TS.48 - Generic eUICC Test Profile for Device Testing) + +See also: https://github.com/GSMATerminals/Generic-eUICC-Test-Profile-for-Device-Testi... + +JAVA card applets +~~~~~~~~~~~~~~~~~ + +The `saip-tool.py` can also be used to manage JAVA-card applets (Application PE) inside a profile package. The user has +the option to add, remove and inspect applications and their instances. In the following we will discuss a few JAVA-card +related use-cases of `saip-tool.py` + +NOTE: see also `contrib` folder for script examples (`saip-tool_example_*.sh`) + +Inserting applications +---------------------- + +An application is usually inserted in two steps. In the first step, the application PE is created and populated with +the executable code from a provided `.cap` or `.ijc` file. The user also has to pick a suitable load block AID. + +The application instance, which exists inside the application PE, is created in a second step. Here the user must +reference the load block AID and pick, among other application related parameters, a suitable class and instance AID. + +Example: Adding a JAVA-card applet to an existing profile package +:: + # Step #1: Create the application PE and load the ijc contents from the .cap file: + $ ./contrib/saip-tool.py upp.der add-app --output-file upp_with_app.der --applet-file app.cap --aid '1122334455' + Read 28 PEs from file 'upp.der' + Applying applet file: 'app.cap'... + application PE inserted into PE Sequence after securityDomain PE AID: a000000151000000 + Writing 29 PEs to file 'upp_with_app.der'... + + # Step #2: Create the application instance inside the application PE created in step #1: + $ ./contrib/saip-tool.py upp_with_app.der add-app-inst --output-file upp_with_app_and_instance.der \ + --aid '1122334455' \ + --class-aid '112233445501' \ + --inst-aid '112233445501' \ + --app-privileges '00' \ + --app-spec-pars '00' \ + --uicc-toolkit-app-spec-pars '01001505000000000000000000000000' + Read 29 PEs from file 'upp_with_app.der' + Found Load Package AID: 1122334455, adding new instance AID: 112233445501 to Application PE... + Writing 29 PEs to file 'upp_with_app_and_instance.der'... + +NOTE: The parameters of the sub-commands `add-app` and `add-app-inst` are application specific. It is up to the application +developer to pick parameters that suit the application correctly. For an exact command reference see section +`saip-tool syntax`. For parameter details see `TCA eUICC Profile Package: Interoperable Format Technical Specification`, +section 8.7 and ETSI TS 102 226, section 8.2.1.3.2 + + +Inspecting applications +----------------------- + +To inspect the application PE contents of an existing profile package, sub-command `info` with parameter '--apps' can +be used. This command lists out all application and their parameters in detail. This allows an application developer +to check if the applet insertaion was carried out as expected. + +Example: Listing applications and their parameters +:: + $ ./contrib/saip-tool.py upp_with_app_and_instance.der info --apps + Read 29 PEs from file 'upp_with_app_and_instance.der' + Application #0: + loadBlock: + loadPackageAID: '1122334455' (5 bytes) + loadBlockObject: '01000fdecaffed010204000105d07002ca440200...681080056810a00633b44104b431066800a10231' (569 bytes) + instanceList[0]: + applicationLoadPackageAID: '1122334455' (5 bytes) + classAID: '112233445501' (8 bytes) + instanceAID: '112233445501' (8 bytes) + applicationPrivileges: '00' (1 bytes) + lifeCycleState: '07' (1 bytes) + applicationSpecificParametersC9: '00' (1 bytes) + applicationParameters: + uiccToolkitApplicationSpecificParametersField: '01001505000000000000000000000000' (16 bytes) + +In case further analysis with external tools or transfer of applications from one profile package to another is +necessary, the executable code in the `loadBlockObject` field can be extracted to an `.ijc` or an `.cap` file. + +Example: Extracting applications from a profile package +:: + $ ./contrib/saip-tool.py upp_with_app_and_instance.der extract-apps --output-dir ./apps --format ijc + Read 29 PEs from file 'upp_with_app_and_instance.der' + Writing Load Package AID: 1122334455 to file ./apps/8949449999999990023f-1122334455.ijc + + +Removing applications +--------------------- + +An application PE can be removed using sub-command `remove-app`. The user passes the load package AID as parameter. Then +`saip-tool.py` will search for the related application PE and delete it from the PE sequence. + +Example: Remove an application from a profile package +:: + $ ./contrib/saip-tool.py upp_with_app_and_instance.der remove-app --output-file upp_without_app.der --aid '1122334455' + Read 29 PEs from file 'upp_with_app_and_instance.der' + Found Load Package AID: 1122334455, removing related PE (id=23) from Sequence... + Removing PE application (id=23) from Sequence... + Writing 28 PEs to file 'upp_without_app.der'... + +In some cases it is useful to remove only an instance from an existing application PE. This may be the case when the +an application developer wants to modify parameters of an application by removing and re-adding the instance. The +operation basically rolls the state back to step 1 explained in section :ref:`Inserting applications` + +Example: Remove an application instance from an application PE +:: + $ ./contrib/saip-tool.py upp_with_app_and_instance.der remove-app-inst --output-file upp_without_app.der --aid '1122334455' --inst-aid '112233445501' + Read 29 PEs from file 'upp_with_app_and_instance.der' + Found Load Package AID: 1122334455, removing instance AID: 112233445501 from Application PE... + Removing instance from Application PE... + Writing 29 PEs to file 'upp_with_app.der'... + + +saip-tool syntax +~~~~~~~~~~~~~~~~ + +.. argparse:: + :module: contrib.saip-tool + :func: parser + :prog: contrib/saip-tool.py diff --git a/pySim/esim/saip/__init__.py b/pySim/esim/saip/__init__.py index d6b41ba..9ccf134 100644 --- a/pySim/esim/saip/__init__.py +++ b/pySim/esim/saip/__init__.py @@ -26,7 +26,7 @@ from pySim import javacard from osmocom.utils import b2h, h2b, Hexstr from osmocom.tlv import BER_TLV_IE, bertlv_parse_tag, bertlv_parse_len -from osmocom.construct import build_construct, parse_construct, GreedyInteger +from osmocom.construct import build_construct, parse_construct, GreedyInteger, GreedyBytes, StripHeaderAdapter
from pySim import ts_102_222 from pySim.utils import dec_imsi @@ -1096,6 +1096,49 @@ def __init__(self, decoded: Optional[dict] = None, **kwargs): super().__init__(decoded, **kwargs)
+ @classmethod + def from_file(cls, + filename:str, + aid:Hexstr, + sd_aid:Hexstr = None, + non_volatile_code_limit:int = None, + volatile_data_limit:int = None, + non_volatile_data_limit:int = None, + hash_value:Hexstr = None) -> 'ProfileElementApplication': + """Fill contents of application ProfileElement from a .cap file.""" + + inst = cls() + Construct_data_limit = StripHeaderAdapter(GreedyBytes, 4, steps = [2,4]) + + if filename.lower().endswith('.cap'): + cap = javacard.CapFile(filename) + load_block_object = cap.get_loadfile() + elif filename.lower().endswith('.ijc'): + fd = open(filename, 'rb') + load_block_object = fd.read() + else: + raise ValueError('Invalid file type, file must either .cap or .ijc') + + # Mandatory + inst.decoded['loadBlock'] = { + 'loadPackageAID': h2b(aid), + 'loadBlockObject': load_block_object + } + + # Optional + if sd_aid: + inst.decoded['loadBlock']['securityDomainAID'] = h2b(sd_aid) + if non_volatile_code_limit: + inst.decoded['loadBlock']['nonVolatileCodeLimitC6'] = Construct_data_limit.build(non_volatile_code_limit) + if volatile_data_limit: + inst.decoded['loadBlock']['volatileDataLimitC7'] = Construct_data_limit.build(volatile_data_limit) + if non_volatile_data_limit: + inst.decoded['loadBlock']['nonVolatileDataLimitC8'] = Construct_data_limit.build(non_volatile_data_limit) + if hash_value: + inst.decoded['loadBlock']['hashValue'] = h2b(hash_value) + + return inst + def to_file(self, filename:str): """Write loadBlockObject contents of application ProfileElement to a .cap or .ijc file."""
@@ -1111,6 +1154,69 @@ else: raise ValueError('Invalid file type, file must either .cap or .ijc')
+ def add_instance(self, + aid:Hexstr, + class_aid:Hexstr, + inst_aid:Hexstr, + app_privileges:Hexstr, + app_spec_pars:Hexstr, + uicc_toolkit_app_spec_pars:Hexstr = None, + uicc_access_app_spec_pars:Hexstr = None, + uicc_adm_access_app_spec_pars:Hexstr = None, + volatile_memory_quota:Hexstr = None, + non_volatile_memory_quota:Hexstr = None, + process_data:list[Hexstr] = None): + """Create a new instance and add it to the instanceList""" + + # Mandatory + inst = {'applicationLoadPackageAID': h2b(aid), + 'classAID': h2b(class_aid), + 'instanceAID': h2b(inst_aid), + 'applicationPrivileges': h2b(app_privileges), + 'applicationSpecificParametersC9': h2b(app_spec_pars)} + + # Optional + if uicc_toolkit_app_spec_pars or uicc_access_app_spec_pars or uicc_adm_access_app_spec_pars: + inst['applicationParameters'] = {} + if uicc_toolkit_app_spec_pars: + inst['applicationParameters']['uiccToolkitApplicationSpecificParametersField'] = \ + h2b(uicc_toolkit_app_spec_pars) + if uicc_access_app_spec_pars: + inst['applicationParameters']['uiccAccessApplicationSpecificParametersField'] = \ + h2b(uicc_access_app_spec_pars) + if uicc_adm_access_app_spec_pars: + inst['applicationParameters']['uiccAdministrativeAccessApplicationSpecificParametersField'] = \ + h2b(uicc_adm_access_app_spec_pars) + if volatile_memory_quota is not None or non_volatile_memory_quota is not None: + inst['systemSpecificParameters'] = {} + Construct_data_limit = StripHeaderAdapter(GreedyBytes, 4, steps = [2,4]) + if volatile_memory_quota is not None: + inst['systemSpecificParameters']['volatileMemoryQuotaC7'] = \ + Construct_data_limit.build(volatile_memory_quota) + if non_volatile_memory_quota is not None: + inst['systemSpecificParameters']['nonVolatileMemoryQuotaC8'] = \ + Construct_data_limit.build(non_volatile_memory_quota) + if len(process_data) > 0: + inst['processData'] = [] + for proc in process_data: + inst['processData'].append(h2b(proc)) + + # Append created instance to instance list + if 'instanceList' not in self.decoded.keys(): + self.decoded['instanceList'] = [] + self.decoded['instanceList'].append(inst) + + def remove_instance(self, inst_aid:Hexstr): + """Remove an instance from the instanceList""" + inst_list = self.decoded.get('instanceList', []) + for inst in enumerate(inst_list): + if b2h(inst[1].get('instanceAID', None)) == inst_aid: + inst_list.pop(inst[0]) + return + raise ValueError("instance AID: '%s' not present in instanceList, cannot remove instance" % inst[1]) + + + class ProfileElementRFM(ProfileElement): type = 'rfm'