Compare commits

..

30 Commits

Author SHA1 Message Date
Neels Hofmeyr e82ae66d84 Revert "esim/http_json_api: extend JSON API with server functionality"
This reverts commit e00c0becca.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr e345f31833 Revert "esim/http_json_api: add missing apidoc"
This reverts commit 0a1c5a27d7.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 504c83662a Revert "http_json_api: Only require Content-Type if response body is non-empty"
This reverts commit e0a9e73267.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 9677a8ecf2 Revert "esim/http_json_api: add alternative API interface"
This reverts commit f9d7c82b4d.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 7f87563d09 Revert "esim/http_json_api: add alternative API interface (follow up)"
This reverts commit 8b2a49aa8e.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 5d079d085f Revert "esim/http_json_api: allow URL rewriting"
This reverts commit 0634f77308.
2026-06-26 21:51:06 +02:00
Neels Hofmeyr fd8c2ba0b0 personalization: add GfmSuciRi, GfmSuciCalcInfo, and test
Change-Id: I1b69debf5992aa715171b43b30864dc152dc556f
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 27dfae5efc SAIP2.1_gfmsuci.der
Change-Id: I69354754013e4a98d86a9a8abdd7a6a36eb95177
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 785ebfdd06 debug log for rebuild_mandatory_services()
Change-Id: Ie53c84247a9524a81b475b3b02b5330207c15601
2026-06-26 21:51:06 +02:00
Neels Hofmeyr 0d8a43904f saip/personalization: add EuiccMandatoryServiceParam for 3 services
Even though the eUICC-Mandatory-services are always set by
saip.PES.rebuild_mandatory_services(), these new params are useful to
audit and validate the values as present in a given DER.

Change-Id: Icddeb2488c4a024c6ee5afcc1b6c8cc0e436c43c
2026-06-26 21:48:49 +02:00
Neels Hofmeyr 664d69db65 personalization: SUCI: ff-pad remaining file size
Change-Id: I9465eac9269e3f76dddd467109f547489a0feb93
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 0175b1045b saip.personalization: add SAIP 2.3 SUCI parameters
Change-Id: I3c0793b8a67bbd0c8247784bd3b5cbd265f94ec2
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 945dfb0129 tweak test_configurable_parameters.py: add iff_present flag
apply a parameter only when it exists in the template, will be useful
for suci

Change-Id: I5811ecde4c4e880bb8dbd22fffe23faafcfe36ad
2026-06-26 21:47:48 +02:00
Neels Hofmeyr f1447ea55b tweak test_configurable_parameters.py: show value found in template
Change-Id: If9039bbb8547ee24ae784a932f60cd5de6c9247b
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 7f32641008 tweak test_configurable_parameters.py: saner output composition.
Change-Id: Id3e3b46b2b3d7919a75c620803ce28d2a715008b
2026-06-26 21:47:48 +02:00
Neels Hofmeyr c01d7a70e1 TuakNumberOfKeccak: remove numeric_base to hide random number sources
Change-Id: I913878e3f05ad1e39ff45da75c67582a6a1f930f
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 8c81e70225 saip/batch: add blacklist to to_csv_rows()
Change-Id: I5e567e59a007cf4b5d75a4dcea5371ff4404bf24
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 434cc45259 ConfigurableParameter.get_typical_input_len: limit to 10 lines
Change-Id: Ia3d79e786f397a02bf2a8fafac5030d1198d9f76
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 434669e36c personalization: EF_SMSP: keep same length as found in template
Change-Id: Id24752101ae82c4986209f4103cc9cbdcce8ce1d
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 26ea0d1656 saip.batch: log parameter errors
Change-Id: I6a46b2dc9018078ab8361226d1e6b50d3b4e1aaa
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 7362525f8e personalization: add MncLen ConfigurableParameter
Implement as EnumParam because it only has two possible values

Change-Id: I6c600faeab00ffb072acbe94c9a8b2d1397c07d3
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 5e543f5566 personalization: EnumParam: implement as value_map, not enum.IntEnum
EnumParam is for labels shown in the UI, not an enum to be used in
python code (what the python enum module is written for). So it is
semantically wrong to use enum.IntEnum.

- the label should be allowed to be any string, not just valid python
  identifiers. For example, a label like "SUCI-in-USIM" should be
  possible.

- we should not "leak" UI labels into the python namespace.

- the value should be allowed to be any type, so that each
  ConfigurableParameter implementation can use whichever is its internal
  native type.

History: this patch is the original version of EnumParam, which was
modified during CR to use enum.IntEnum. However, newer
ConfigurableParameters coming up don't match well:
- MncLen (labels "2" and "3")
- EuiccMandatoryServiceParam (values True and False)
- EfUstServiceParam like SuciInUsim (labels "SUCI-in-UE" and
  "SUCI-in-USIM")

So this brings back the original capability for any label string, and
any type of value.

Change-Id: I690ceccf0ec7ef7067bcaa5cec1303cdaf0f78a4
2026-06-26 21:47:48 +02:00
Neels Hofmeyr e2ec0347f6 esim/http_json_api.py: support text/plain response Content-Type
Allow returning text/plain Content-Types as 'data' output argument.

So far, all the esim/http_json_api functions require a JSON response.
However, a specific vendor has a list function where the request is JSON
but the response is text/plain CSV data. Allow and return in a dict.

Change-Id: Iba6e4cef1048b376050a435a900c0f395655a790
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 7a1c506ba9 saip.SdKey*: remove transitional name mapping
Now, finally, all SdKey classes have a unified logical naming scheme.
Revert the transitional name mapping.

Change-Id: Ic185af4a903c2211a5361d023af9e7c6fc57ae78
2026-06-26 21:47:48 +02:00
Neels Hofmeyr cd7a788d6d saip.SdKey*: transitional name mapping
To help existing applications transition to a common naming scheme for
the SdKey classes, offer this intermediate result, where the SdKey
classes' .name are still unchanged as before generating them.

Change-Id: I974cb6c393a2ed2248a6240c2722d157e9235c33
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 7fcd21dd4f ts_31_102.py: EF_SUCI_Calc_Info(TransparentEF): fix len test
while len(foo):

throws an exception when foo == None.
Instead doing

    while foo:

fixes a problem when reading in empty SUCI calc info data, e.g. from
TS48v7.0_SAIP2.3_BERTLV_SUCI_NoRAMRFM.der.

Change-Id: Ia4e2356d0241d7a6ca399ba7e8be7f27ec836104
2026-06-26 21:47:48 +02:00
Neels Hofmeyr a23f06ba4c typo 'concetenation' in personalization.py
Change-Id: I51345db014335e8a70a7437a9cad5a3e47570a95
2026-06-26 21:47:48 +02:00
Neels Hofmeyr 0ed72f6ee7 test_configurable_parameters: test less templates
Change-Id: Ib75b6919a3acfddd99bf9baa9b6847ef731b9e67
2026-06-26 21:47:48 +02:00
Neels Hofmeyr c5f6ba83c2 saip BatchPersonalization: call rebuild_mandatory_services()
Particular reason: when manipulating the 5G SUCI parameters, the
mandatory services get-identity, profile-a-x25519 and profile-b-p256 may
need to be reconfigured.

In general, it is a good idea to run these checks anyway.

Change-Id: I5e6eef0f1845a25cddb03af8d16c40e305bcdc1f
2026-06-26 20:22:59 +02:00
Neels Hofmeyr d23e3fdd44 saip.PES.rebuild_mandatory_services(): set 5G get-identity, profile-a-x25519, profile-b-p256
Related: SYS#8096 SYS#8037
Change-Id: Ibc29c6437c5c92e2b14938b733156536863465c1
2026-06-26 20:21:10 +02:00
7 changed files with 92 additions and 92 deletions
+48 -30
View File
@@ -24,21 +24,21 @@ import traceback
import re
import cmd2
from packaging import version
from cmd2 import style
import logging
from pySim.log import PySimLogger
from osmocom.utils import auto_uint8
# cmd2 >= 3.0 replaced Fg + style() with Color + stylize()
if version.parse(cmd2.__version__) >= version.parse("3.0.0"):
from cmd2 import Color, stylize # pylint: disable=no-name-in-module
RED = Color.RED
YELLOW = Color.YELLOW
LIGHT_RED = Color.BRIGHT_RED
LIGHT_GREEN = Color.BRIGHT_GREEN
def style(text, fg=None, bg=None, bold=False): # pylint: disable=function-redefined
return stylize(text, fg) if fg else text
# cmd2 >= 2.3.0 has deprecated the bg/fg in favor of Bg/Fg :(
if version.parse(cmd2.__version__) < version.parse("2.3.0"):
from cmd2 import fg, bg # pylint: disable=no-name-in-module
RED = fg.red
YELLOW = fg.yellow
LIGHT_RED = fg.bright_red
LIGHT_GREEN = fg.bright_green
else:
from cmd2 import style, Fg # pylint: disable=no-name-in-module
from cmd2 import Fg, Bg # pylint: disable=no-name-in-module
RED = Fg.RED
YELLOW = Fg.YELLOW
LIGHT_RED = Fg.LIGHT_RED
@@ -76,19 +76,43 @@ from pySim.app import init_card
log = PySimLogger.get(Path(__file__).stem)
class PysimApp(cmd2.Cmd):
class Cmd2Compat(cmd2.Cmd):
"""Backwards-compatibility wrapper around cmd2.Cmd to support older and newer
releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
def run_editor(self, file_path: Optional[str] = None) -> None:
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
return self._run_editor(file_path) # pylint: disable=no-member
else:
return super().run_editor(file_path) # pylint: disable=no-member
class Settable2Compat(cmd2.Settable):
"""Backwards-compatibility wrapper around cmd2.Settable to support older and newer
releases. See https://github.com/python-cmd2/cmd2/blob/master/CHANGELOG.md"""
def __init__(self, name, val_type, description, settable_object, **kwargs):
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
super().__init__(name, val_type, description, **kwargs) # pylint: disable=no-value-for-parameter
else:
super().__init__(name, val_type, description, settable_object, **kwargs) # pylint: disable=too-many-function-args
class PysimApp(Cmd2Compat):
CUSTOM_CATEGORY = 'pySim Commands'
BANNER = """Welcome to pySim-shell!
(C) 2021-2023 by Harald Welte, sysmocom - s.f.m.c. GmbH and contributors
Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/shell.html """
def __init__(self, verbose, card, rs, sl, ch, script=None):
if version.parse(cmd2.__version__) < version.parse("2.0.0"):
kwargs = {'use_ipython': True}
else:
kwargs = {'include_ipy': True}
self.verbose = verbose
PySimLogger.setup(self.poutput, {logging.WARN: YELLOW})
self._onchange_verbose('verbose', False, self.verbose)
# pylint: disable=unexpected-keyword-arg
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
auto_load_commands=False, startup_script=script, include_ipy=True)
auto_load_commands=False, startup_script=script, **kwargs)
self.intro = style(self.BANNER, fg=RED)
self.default_category = 'pySim-shell built-in commands'
self.card = None
@@ -104,24 +128,18 @@ Online manual available at https://downloads.osmocom.org/docs/pysim/master/html/
self.apdu_trace = False
self.apdu_strict = False
self.add_settable(cmd2.Settable('numeric_path', bool,
'Print File IDs instead of names',
self, onchange_cb=self._onchange_numeric_path))
self.add_settable(cmd2.Settable('conserve_write', bool,
'Read and compare before write',
self, onchange_cb=self._onchange_conserve_write))
self.add_settable(cmd2.Settable('json_pretty_print', bool,
'Pretty-Print JSON output',
self))
self.add_settable(cmd2.Settable('apdu_trace', bool,
'Trace and display APDUs exchanged with card',
self, onchange_cb=self._onchange_apdu_trace))
self.add_settable(cmd2.Settable('apdu_strict', bool,
'Strictly apply APDU format according to ISO/IEC 7816-3, table 12',
self))
self.add_settable(cmd2.Settable('verbose', bool,
'Enable/disable verbose logging',
self, onchange_cb=self._onchange_verbose))
self.add_settable(Settable2Compat('numeric_path', bool, 'Print File IDs instead of names', self,
onchange_cb=self._onchange_numeric_path))
self.add_settable(Settable2Compat('conserve_write', bool, 'Read and compare before write', self,
onchange_cb=self._onchange_conserve_write))
self.add_settable(Settable2Compat('json_pretty_print', bool, 'Pretty-Print JSON output', self))
self.add_settable(Settable2Compat('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self,
onchange_cb=self._onchange_apdu_trace))
self.add_settable(Settable2Compat('apdu_strict', bool,
'Strictly apply APDU format according to ISO/IEC 7816-3, table 12', self))
self.add_settable(Settable2Compat('verbose', bool,
'Enable/disable verbose logging', self,
onchange_cb=self._onchange_verbose))
self.equip(card, rs)
def equip(self, card, rs):
+38 -49
View File
@@ -300,51 +300,6 @@ class ADF_ARAM(CardADF):
'major': v_major, 'minor': v_minor, 'patch': v_patch}}])
return ADF_ARAM.xceive_apdu_tlv(scc, '80cadf21', cmd_do, ResponseAramConfigDO)
@staticmethod
def store_ref_ar_do(scc, aid:Hexstr, aid_empty:bool, device_app_id:Hexstr, pkg_ref:str,
apdu_filter:Hexstr, apdu_never:bool, apdu_always:bool,
nfc_always:bool, nfc_never:bool, android_permissions:Hexstr):
# REF
ref_do_content = []
if aid is not None:
ref_do_content += [{'aid_ref_do': aid}]
elif aid_empty:
ref_do_content += [{'aid_ref_empty_do': None}]
ref_do_content += [{'dev_app_id_ref_do': device_app_id}]
if pkg_ref:
ref_do_content += [{'pkg_ref_do': {'package_name_string': pkg_ref}}]
# AR
ar_do_content = []
if apdu_never:
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'never'}}]
elif apdu_always:
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}]
elif apdu_filter:
if len(apdu_filter) % 16:
raise ValueError(f'Invalid non-modulo-16 length of APDU filter: {len(apdu_filter)}')
offset = 0
apdu_filter_list = []
while offset < len(apdu_filter):
apdu_filter_list += [{'header': apdu_filter[offset:offset+8],
'mask': apdu_filter[offset+8:offset+16]}]
offset += 16 # Move offset to the beginning of the next apdu_filter object
ar_do_content += [{'apdu_ar_do': {'apdu_filter': apdu_filter_list}}]
if nfc_never:
ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'never'}}]
elif nfc_always:
ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'always'}}]
if android_permissions:
ar_do_content += [{'perm_ar_do': {'permissions': android_permissions}}]
d = [{'ref_ar_do': [{'ref_do': ref_do_content}, {'ar_do': ar_do_content}]}]
csrado = CommandStoreRefArDO()
csrado.from_val_dict(d)
return ADF_ARAM.store_data(scc, csrado)
@staticmethod
def aram_delete_all(scc):
deldo = CommandDelete()
return ADF_ARAM.store_data(scc, deldo)
@with_default_category('Application-Specific Commands')
class AddlShellCommands(CommandSet):
def do_aram_get_all(self, _opts):
@@ -389,15 +344,48 @@ class ADF_ARAM(CardADF):
@cmd2.with_argparser(store_ref_ar_do_parse)
def do_aram_store_ref_ar_do(self, opts):
"""Perform STORE DATA [Command-Store-REF-AR-DO] to store a (new) access rule."""
res_do = ADF_ARAM.store_ref_ar_do(self._cmd.lchan.scc, opts.aid, opts.aid_empty, opts.device_app_id,
opts.pkg_ref, opts.apdu_filter, opts.apdu_never, opts.apdu_always,
opts.nfc_always, opts.nfc_never, opts.android_permissions)
# REF
ref_do_content = []
if opts.aid is not None:
ref_do_content += [{'aid_ref_do': opts.aid}]
elif opts.aid_empty:
ref_do_content += [{'aid_ref_empty_do': None}]
ref_do_content += [{'dev_app_id_ref_do': opts.device_app_id}]
if opts.pkg_ref:
ref_do_content += [{'pkg_ref_do': {'package_name_string': opts.pkg_ref}}]
# AR
ar_do_content = []
if opts.apdu_never:
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'never'}}]
elif opts.apdu_always:
ar_do_content += [{'apdu_ar_do': {'generic_access_rule': 'always'}}]
elif opts.apdu_filter:
if len(opts.apdu_filter) % 16:
raise ValueError(f'Invalid non-modulo-16 length of APDU filter: {len(opts.apdu_filter)}')
offset = 0
apdu_filter = []
while offset < len(opts.apdu_filter):
apdu_filter += [{'header': opts.apdu_filter[offset:offset+8],
'mask': opts.apdu_filter[offset+8:offset+16]}]
offset += 16 # Move offset to the beginning of the next apdu_filter object
ar_do_content += [{'apdu_ar_do': {'apdu_filter': apdu_filter}}]
if opts.nfc_always:
ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'always'}}]
elif opts.nfc_never:
ar_do_content += [{'nfc_ar_do': {'nfc_event_access_rule': 'never'}}]
if opts.android_permissions:
ar_do_content += [{'perm_ar_do': {'permissions': opts.android_permissions}}]
d = [{'ref_ar_do': [{'ref_do': ref_do_content}, {'ar_do': ar_do_content}]}]
csrado = CommandStoreRefArDO()
csrado.from_val_dict(d)
res_do = ADF_ARAM.store_data(self._cmd.lchan.scc, csrado)
if res_do:
self._cmd.poutput_json(res_do.to_dict())
def do_aram_delete_all(self, _opts):
"""Perform STORE DATA [Command-Delete[all]] to delete all access rules."""
res_do = ADF_ARAM.aram_delete_all(self._cmd.lchan.scc)
deldo = CommandDelete()
res_do = ADF_ARAM.store_data(self._cmd.lchan.scc, deldo)
if res_do:
self._cmd.poutput_json(res_do.to_dict())
@@ -406,6 +394,7 @@ class ADF_ARAM(CardADF):
(Proprietary feature that is specific to sysmocom's fork of Bertrand Martels ARA-M implementation.)"""
self._cmd.lchan.scc.send_apdu_checksw('80e2900001A1', '9000')
# SEAC v1.1 Section 4.1.2.2 + 5.1.2.2
sw_aram = {
'ARA-M': {
+1 -1
View File
@@ -1280,7 +1280,7 @@ class SuciCalcInfoParameter(ConfigurableParameter):
default_source = param_source.ConstantSource
allow_types = (str,)
max_len = 4096 # to indicate a large input field to UI renderers
example_input = '{"prot_scheme_id_list": [], "hnet_pubkey_list": []}'
example_input = '{"prot_scheme_id_list": [{"priority": 0, "identifier": 0, "key_index": 0}], "hnet_pubkey_list": []}'
PE_IN_UE = ("df-5gs", "ef-suci-calc-info")
PE_IN_USIM = ("df-saip", "ef-suci-calc-info-usim")
+2 -10
View File
@@ -24,15 +24,7 @@
#
import logging
import cmd2
from packaging import version
if version.parse(cmd2.__version__) >= version.parse("3.0.0"):
from cmd2 import stylize as _stylize # pylint: disable=no-name-in-module
def _style(text, fg=None): # pylint: disable=function-redefined
return _stylize(text, fg) if fg else text
else: # cmd2>=2.6.2
from cmd2 import style as _style # pylint: disable=no-name-in-module
from cmd2 import style
class _PySimLogHandler(logging.Handler):
def __init__(self, log_callback):
@@ -129,7 +121,7 @@ class PySimLogger:
if isinstance(color, str):
PySimLogger.print_callback(color + formatted_message + "\033[0m")
else:
PySimLogger.print_callback(_style(formatted_message, fg = color))
PySimLogger.print_callback(style(formatted_message, fg = color))
else:
PySimLogger.print_callback(formatted_message)
+1 -1
View File
@@ -1,7 +1,7 @@
pyscard
pyserial
pytlv
cmd2>=2.6.2,<4.0
cmd2>=2.6.2,<3.0
jsonpath-ng
construct>=2.10.70
bidict
+1 -1
View File
@@ -21,7 +21,7 @@ setup(
"pyscard",
"pyserial",
"pytlv",
"cmd2 >= 2.6.2, < 4.0",
"cmd2 >= 1.5.0, < 3.0",
"jsonpath-ng",
"construct >= 2.10.70",
"bidict",
@@ -271,6 +271,7 @@ class ConfigurableParameterTest(unittest.TestCase):
'11111111111111111111111111111111'
'22222222222222222222222222222222'),
Paramtest(param_cls=p13n.MncLen,
val='2',
expect_clean_val=2,