laforge has submitted this change. ( https://gerrit.osmocom.org/c/docker-playground/+/39972?usp=email )
Change subject: upgrade to gerrit 3.10.x as 3.9 is EOL on May 12th
......................................................................
upgrade to gerrit 3.10.x as 3.9 is EOL on May 12th
Change-Id: I906b4ff47cf714010697eeb226af826edfbaf911
---
M gerrit/Dockerfile
1 file changed, 1 insertion(+), 1 deletion(-)
Approvals:
fixeria: Looks good to me, approved
pespin: Looks good to me, but someone else must approve
Jenkins Builder: Verified
diff --git a/gerrit/Dockerfile b/gerrit/Dockerfile
index e57c9eb..fa9c163 100644
--- a/gerrit/Dockerfile
+++ b/gerrit/Dockerfile
@@ -1,4 +1,4 @@
-FROM gerritcodereview/gerrit:3.9.7
+FROM gerritcodereview/gerrit:3.10.5
USER root
RUN yum -y install zip unzip patch
--
To view, visit https://gerrit.osmocom.org/c/docker-playground/+/39972?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: docker-playground
Gerrit-Branch: master
Gerrit-Change-Id: I906b4ff47cf714010697eeb226af826edfbaf911
Gerrit-Change-Number: 39972
Gerrit-PatchSet: 2
Gerrit-Owner: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <vyanitskiy(a)sysmocom.de>
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Reviewer: pespin <pespin(a)sysmocom.de>
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-Test…
+
+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'
--
To view, visit https://gerrit.osmocom.org/c/pysim/+/39860?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: merged
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: I41db96f2f0ccc29c1725a92215ce6b17d87b76ce
Gerrit-Change-Number: 39860
Gerrit-PatchSet: 4
Gerrit-Owner: dexter <pmaier(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-CC: fixeria <vyanitskiy(a)sysmocom.de>
Attention is currently required from: laforge.
osmith has posted comments on this change by osmith. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40028?usp=email )
Change subject: buildsystem: remove ttcn3_compiler workaround
......................................................................
Set Ready For Review
--
To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40028?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: Ia50aa3caffeaa85eefba10695096aa23dcb69c93
Gerrit-Change-Number: 40028
Gerrit-PatchSet: 1
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: laforge <laforge(a)osmocom.org>
Gerrit-Comment-Date: Mon, 14 Apr 2025 11:00:51 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: No
Attention is currently required from: osmith.
laforge has posted comments on this change by osmith. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40033?usp=email )
Change subject: buildsystem: make output more readable
......................................................................
Patch Set 2: Code-Review+1
--
To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/40033?usp=email
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings?usp=email
Gerrit-MessageType: comment
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I780106e64089dc24a7ba724f6a94e67417d49c30
Gerrit-Change-Number: 40033
Gerrit-PatchSet: 2
Gerrit-Owner: osmith <osmith(a)sysmocom.de>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge(a)osmocom.org>
Gerrit-Attention: osmith <osmith(a)sysmocom.de>
Gerrit-Comment-Date: Mon, 14 Apr 2025 10:59:41 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes