From ccc9b5a7b27c7983456c359be7d62bdc48490db2 Mon Sep 17 00:00:00 2001 From: Neels Hofmeyr Date: Fri, 12 Jun 2026 02:23:36 +0200 Subject: [PATCH] saip/personalization: add EuiccMandatoryServiceParam for 3 services Change-Id: Icddeb2488c4a024c6ee5afcc1b6c8cc0e436c43c --- pySim/esim/saip/personalization.py | 46 +++++++ .../unittests/test_configurable_parameters.py | 27 ++++ .../unittests/xo/test_configurable_parameters | 120 ++++++++++++++++++ 3 files changed, 193 insertions(+) diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py index 16a5f119..3759e676 100644 --- a/pySim/esim/saip/personalization.py +++ b/pySim/esim/saip/personalization.py @@ -31,6 +31,7 @@ from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_In from pySim.ts_51_011 import EF_SMSP from pySim.esim.saip import param_source from pySim.esim.saip import ProfileElement, ProfileElementSD, ProfileElementSequence +from pySim.esim.saip import ProfileElementHeader from pySim.esim.saip import SecurityDomainKey, SecurityDomainKeyComponent from pySim.global_platform import KeyUsageQualifier, KeyType @@ -1393,3 +1394,48 @@ class SuciCalcInfoUsim(SuciCalcInfoParameter): """SUCI Calculation Information as in section 4.4.11.8 of 3GPP TS 31.102, readable only by USIM (DF-SAIP)""" name = '5G-SUCI-CalcInfo-USIM' suci_calc_info_pe = SuciCalcInfoParameter.PE_IN_USIM + +class EuiccMandatoryServiceParam(EnumParam): + """superclass for managing items of the ProfileHeader / eUICC-Mandatory-services ServicesList""" + service_name = None + value_map = { 'mandatory': True, 'optional': False } + default_source = param_source.ConstantSource + example_input = sorted(value_map.keys())[0] + + @classmethod + def apply_val(cls, pes: ProfileElementSequence, val): + for pe in pes.get_pes_for_type('header'): + assert isinstance(pe, ProfileElementHeader) + if val: + pe.mandatory_service_add(cls.service_name) + else: + # explicitly check to avoid exception when then service is already not present + if pe.mandatory_service_present(cls.service_name): + pe.mandatory_service_remove(cls.service_name) + + @classmethod + def get_values_from_pes(cls, pes: ProfileElementSequence): + for pe in pes.get_pes_for_type('header'): + assert isinstance(pe, ProfileElementHeader) + val = bool(pe.mandatory_service_present(cls.service_name)) + yield { cls.name: cls.map_val_to_name(val) } + +class EuiccMandatoryServiceGetIdentity(EuiccMandatoryServiceParam): + """eUICC Mandatory Services: get-identity. The eUICC must be capable of providing a 5G identity using SUCI-CalcInfo + located in the USIM's DF-SAIP, see parameter 5G-SUCI-CalcInfo-USIM.""" + name = '5G-eUICC-get-identity' + service_name = 'get-identity' + +class EuiccMandatoryServiceProfileA(EuiccMandatoryServiceParam): + """eUICC Mandatory Services: profile-a-x25519. The eUICC must be able to estblish a 5G identity using an X25519 key, + as provided in a profile-A ("identifier": 1) key in SUCI-CalcInfo located in the USIM's DF-SAIP, see parameter + 5G-SUCI-CalcInfo-USIM.""" + name = '5G-eUICC-profile-a-x25519' + service_name = 'profile-a-x25519' + +class EuiccMandatoryServiceProfileB(EuiccMandatoryServiceParam): + """eUICC Mandatory Services: profile-b-p256. The eUICC must be able to estblish a 5G identity using a P256 key, as + provided in a profile-B ("identifier": 2) key in SUCI-CalcInfo located in the USIM's DF-SAIP, see parameter + 5G-SUCI-CalcInfo-USIM.""" + name = '5G-eUICC-profile-b-p256' + service_name = 'profile-b-p256' diff --git a/tests/unittests/test_configurable_parameters.py b/tests/unittests/test_configurable_parameters.py index 8f6b988d..82475e6c 100755 --- a/tests/unittests/test_configurable_parameters.py +++ b/tests/unittests/test_configurable_parameters.py @@ -281,6 +281,33 @@ class ConfigurableParameterTest(unittest.TestCase): val=3, expect_clean_val=3, expect_val='3'), + + Paramtest(param_cls=p13n.EuiccMandatoryServiceGetIdentity, + val='mandatory', + expect_clean_val=True, + expect_val='mandatory'), + Paramtest(param_cls=p13n.EuiccMandatoryServiceGetIdentity, + val='optional', + expect_clean_val=False, + expect_val='optional'), + + Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileA, + val='mandatory', + expect_clean_val=True, + expect_val='mandatory'), + Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileA, + val='optional', + expect_clean_val=False, + expect_val='optional'), + + Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileB, + val='mandatory', + expect_clean_val=True, + expect_val='mandatory'), + Paramtest(param_cls=p13n.EuiccMandatoryServiceProfileB, + val='optional', + expect_clean_val=False, + expect_val='optional'), ] Paramtest.iff_present_default = True diff --git a/tests/unittests/xo/test_configurable_parameters b/tests/unittests/xo/test_configurable_parameters index 22b24868..77dd1647 100644 --- a/tests/unittests/xo/test_configurable_parameters +++ b/tests/unittests/xo/test_configurable_parameters @@ -214,6 +214,36 @@ ok: TS48v5_SAIP2.1A_NoBERTLV.der MncLen(val=3:int) previous value: ['2'] read_back_val={'MNC-LEN': '3'}:{str} +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-get-identity': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-get-identity': 'optional'}:{str} + +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceProfileA(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-a-x25519': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceProfileA(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-a-x25519': 'optional'}:{str} + +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceProfileB(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-b-p256': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1A_NoBERTLV.der EuiccMandatoryServiceProfileB(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-b-p256': 'optional'}:{str} + ok: TS48v5_SAIP2.1A_NoBERTLV.der SuciActive(val='SUCI-on':str) clean_val=True:bool previous value: ['SUCI-on'] @@ -1094,6 +1124,36 @@ ok: TS48v5_SAIP2.3_BERTLV_SUCI.der MncLen(val=3:int) previous value: ['2'] read_back_val={'MNC-LEN': '3'}:{str} +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceGetIdentity(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-get-identity': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceGetIdentity(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-get-identity': 'optional'}:{str} + +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceProfileA(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-a-x25519': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceProfileA(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-a-x25519': 'optional'}:{str} + +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceProfileB(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-b-p256': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_BERTLV_SUCI.der EuiccMandatoryServiceProfileB(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-b-p256': 'optional'}:{str} + ok: TS48v5_SAIP2.3_BERTLV_SUCI.der SuciActive(val='SUCI-on':str) clean_val=True:bool previous value: ['SUCI-on'] @@ -1974,6 +2034,36 @@ ok: TS48v5_SAIP2.1B_NoBERTLV.der MncLen(val=3:int) previous value: ['2'] read_back_val={'MNC-LEN': '3'}:{str} +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-get-identity': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-get-identity': 'optional'}:{str} + +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceProfileA(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-a-x25519': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceProfileA(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-a-x25519': 'optional'}:{str} + +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceProfileB(val='mandatory':str) + clean_val=True:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-b-p256': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.1B_NoBERTLV.der EuiccMandatoryServiceProfileB(val='optional':str) + clean_val=False:bool + previous value: ['optional'] + read_back_val={'5G-eUICC-profile-b-p256': 'optional'}:{str} + ok: TS48v5_SAIP2.1B_NoBERTLV.der SuciActive(val='SUCI-on':str) clean_val=True:bool previous value: ['SUCI-on'] @@ -2854,6 +2944,36 @@ ok: TS48v5_SAIP2.3_NoBERTLV.der MncLen(val=3:int) previous value: ['2'] read_back_val={'MNC-LEN': '3'}:{str} +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-get-identity': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceGetIdentity(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-get-identity': 'optional'}:{str} + +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceProfileA(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-a-x25519': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceProfileA(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-a-x25519': 'optional'}:{str} + +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceProfileB(val='mandatory':str) + clean_val=True:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-b-p256': 'mandatory'}:{str} + +ok: TS48v5_SAIP2.3_NoBERTLV.der EuiccMandatoryServiceProfileB(val='optional':str) + clean_val=False:bool + previous value: ['mandatory'] + read_back_val={'5G-eUICC-profile-b-p256': 'optional'}:{str} + ok: TS48v5_SAIP2.3_NoBERTLV.der SuciActive(val='SUCI-on':str) clean_val=True:bool previous value: ['SUCI-on']