neels has uploaded this change for review.
saip/personalization: add SUCI parameters for SAIP 2.1 (GFM)
Add GfmSuciCalcInfo, GfmSuciRi, and test.
Change-Id: I1b69debf5992aa715171b43b30864dc152dc556f
Jenkins: skip-card-test
---
M pySim/esim/saip/personalization.py
M tests/unittests/test_configurable_parameters.py
M tests/unittests/xo/test_configurable_parameters
3 files changed, 244 insertions(+), 9 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/69/43869/1
diff --git a/pySim/esim/saip/personalization.py b/pySim/esim/saip/personalization.py
index d6fa08d..479df58 100644
--- a/pySim/esim/saip/personalization.py
+++ b/pySim/esim/saip/personalization.py
@@ -26,7 +26,7 @@
from osmocom.tlv import camel_to_snake
from osmocom.utils import hexstr
from pySim.utils import enc_iccid, dec_iccid, enc_imsi, dec_imsi, h2b, b2h, rpad, sanitize_iccid
-from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_Info
+from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_Info, DF_USIM_5GS
from pySim.ts_51_011 import EF_SMSP
from pySim.esim.saip import param_source
from pySim.esim.saip import ProfileElement, ProfileElementSD, ProfileElementSequence
@@ -34,6 +34,11 @@
from pySim.esim.saip import SecurityDomainKey, SecurityDomainKeyComponent
from pySim.global_platform import KeyUsageQualifier, KeyType
+# optimization: instantiate class instance to get the fid only once.
+file_path_df_5gs = bytes.fromhex(DF_USIM_5GS().fid)
+fid_ri = bytes.fromhex(EF_Routing_Indicator().fid)
+fid_sucici = bytes.fromhex(EF_SUCI_Calc_Info().fid)
+
def unrpad(s: hexstr, c='f') -> hexstr:
return hexstr(s.rstrip(c))
@@ -1350,6 +1355,21 @@
def apply_val(cls, pes: ProfileElementSequence, val):
cls._apply_suci(pes, val, *cls.suci_calc_info_pe)
+ @staticmethod
+ def normalize_sucici(sucici:dict):
+ """Normalize the CalcInfo dict so it can be json encoded:
+ convert bytes to hex strings."""
+ if not sucici:
+ sucici = {}
+
+ for hnet_pubkey in sucici.get('hnet_pubkey_list', ()):
+ val = hnet_pubkey['hnet_pubkey']
+ if isinstance(val, bytes):
+ val = b2h(val)
+ hnet_pubkey['hnet_pubkey'] = val
+
+ return sucici
+
@classmethod
def _get_suci(cls, pes: ProfileElementSequence, pe_type="df-5gs", pe_file="ef-suci-calc-info"):
for pe in pes.get_pes_for_type(pe_type):
@@ -1359,15 +1379,8 @@
ef_sucici = EF_SUCI_Calc_Info()
sucici = ef_sucici.decode_bin(f_sucici.body)
- if not sucici:
- sucici = {}
-
# normalize to string (bytes cannot go into json)
- for hnet_pubkey in sucici.get('hnet_pubkey_list', ()):
- val = hnet_pubkey['hnet_pubkey']
- if isinstance(val, bytes):
- val = b2h(val)
- hnet_pubkey['hnet_pubkey'] = val
+ sucici = cls.normalize_sucici(sucici)
yield { cls.name: json.dumps(sucici) }
@@ -1385,6 +1398,153 @@
name = '5G-SUCI-CalcInfo-USIM'
suci_calc_info_pe = SuciCalcInfoParameter.PE_IN_USIM
+def gfm_find(pes: ProfileElementSequence, file_path:bytes, ef_fid:bytes):
+ """look through genericFileManagement PE and return the fmc list with start and end indexes as
+ (fmc_list, first_idx, after_last_idx)
+ so that fmc_list[first_idx:after_last_idx] is the slice of file management commands relevant to the given
+ file_path/ef_fid.
+ """
+ for pe in pes.get_pes_for_type('genericFileManagement'):
+ path_match = False
+ creating_fid = False
+
+ for fmc in pe.decoded['fileManagementCMD']:
+ first = None
+ last = None
+
+ for idx in range(len(fmc)):
+ cmd, arg = fmc[idx]
+
+ if cmd == 'filePath':
+ path_match = (arg == file_path)
+ if not path_match:
+ creating_fid = False
+ elif path_match and cmd == 'createFCP':
+ creating_fid = (arg.get('fileID') == ef_fid)
+ if creating_fid:
+ if first is None:
+ first = idx
+ last = idx
+ first = min(first, idx)
+ last = max(last, idx)
+
+ if first is not None:
+ yield fmc, first, last + 1
+
+# genericFileManagement 5G params
+
+def pes_get_adf_fid(pes:ProfileElementSequence, naa_name="usim", adf_name="adf-usim"):
+ adf = pes.get_pe_for_type(naa_name)
+ return adf.decoded[adf_name][0][1]['fileID']
+
+def mk_adf_df_path(pes, naa:str, adf:str, file_path:bytes) -> bytes:
+ adf_file_id = pes_get_adf_fid(pes, naa, adf)
+ return b''.join((adf_file_id, file_path))
+
+def gfm_get_file_content(pes: ProfileElementSequence, naa:str, adf:str, file_path:bytes, ef_fid:bytes) -> bytes:
+ '''find a given file in the genericFileManagement section, and return the bytes from the first fillFileContent
+ item.
+ TODO: implement File.from_gfm() and return the full resulting bytes?
+ '''
+ adf_df_path = mk_adf_df_path(pes, naa, adf, file_path)
+
+ data = []
+ for fmc, first_idx, after_last_idx in gfm_find(pes, adf_df_path, ef_fid):
+ assert fmc[first_idx][0] == 'createFCP'
+ assert after_last_idx > first_idx
+
+ idx = first_idx + 1
+ while idx < after_last_idx:
+ if fmc[idx][0] == 'fillFileContent':
+ data.append(fmc[idx][1])
+ idx += 1
+
+ return data
+
+def gfm_set_file_content(pes: ProfileElementSequence, naa:str, adf:str, file_path:bytes, ef_fid:bytes, file_content:bytes) -> int:
+ adf_df_path = mk_adf_df_path(pes, naa, adf, file_path)
+
+ found = 0
+ for fmc, first_idx, after_last_idx in gfm_find(pes, adf_df_path, ef_fid):
+ assert fmc[first_idx][0] == 'createFCP'
+ assert after_last_idx > first_idx
+
+ new_fmc = [
+ fmc[first_idx],
+ ('fillFileContent', file_content),
+ ]
+ new_fmc[0][1]['efFileSize'] = bytes((len(file_content), ))
+
+ fmc[first_idx:after_last_idx] = new_fmc
+
+ found += 1
+ return found
+
+class GfmSuciRi(SuciRi):
+ """SUCI Routing Indicator as in section 4.4.11.11 of 3GPP TS 31.102,
+ applied via General File Management. Intended for SAIP 2.1 profiles."""
+ name = 'GFM-5G-SUCI-RI'
+
+ @classmethod
+ def apply_val(cls, pes: ProfileElementSequence, val):
+ ri = {
+ "routing_indicator": str(val),
+ "rfu": "ffff"
+ }
+ ef_ri = EF_Routing_Indicator()
+ found = gfm_set_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri,
+ ef_ri.encode_bin(ri))
+ if not found:
+ raise ValueError(f"No target file found, Cannot apply {cls.name} = {ri}")
+
+ data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri)
+ val = ef_ri.decode_bin(b''.join(data))
+
+ @classmethod
+ def get_values_from_pes(cls, pes: ProfileElementSequence):
+ data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_ri)
+ if not data:
+ return
+
+ data = b''.join(data)
+ if not data:
+ return
+
+ ef_ri = EF_Routing_Indicator()
+ ri = ef_ri.decode_bin(data)
+ yield { cls.name: ri.get(cls.KEY_RI) }
+
+class GfmSuciCalcInfoUe(SuciCalcInfoUe):
+ """SUCI Calculation Information as in section 4.4.11.8 of 3GPP TS 31.102, readable by UE (DF-5GS),
+ applied via General File Management. Intended for SAIP 2.1 profiles."""
+ name = 'GFM-5G-SUCI-CalcInfo-UE'
+
+ @classmethod
+ def apply_val(cls, pes: ProfileElementSequence, val):
+ if not isinstance(val, dict):
+ raise ValueError("val should be a dict, after 'val = SuciCalcInfoParameter.validate_val(val)'")
+
+ ef_sucici = EF_SUCI_Calc_Info()
+ body = ef_sucici.encode_bin(val)
+ gfm_set_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_sucici,
+ body)
+
+ @classmethod
+ def get_values_from_pes(cls, pes: ProfileElementSequence):
+ data = gfm_get_file_content(pes, 'usim', 'adf-usim', file_path_df_5gs, fid_sucici)
+ if not data:
+ return
+
+ data = b''.join(data)
+ if not data:
+ return
+
+ ef_sucici = EF_SUCI_Calc_Info()
+ sucici = ef_sucici.decode_bin(data)
+ sucici = cls.normalize_sucici(sucici)
+ yield { cls.name: json.dumps(sucici) }
+
+
class EuiccMandatoryServiceParam(EnumParam):
"""superclass for managing items of the ProfileHeader / eUICC-Mandatory-services ServicesList"""
service_name = None
diff --git a/tests/unittests/test_configurable_parameters.py b/tests/unittests/test_configurable_parameters.py
index 15f434a..54fe4fc 100755
--- a/tests/unittests/test_configurable_parameters.py
+++ b/tests/unittests/test_configurable_parameters.py
@@ -358,6 +358,21 @@
expect_clean_val=sucici,
expect_val={'5G-SUCI-CalcInfo-USIM': json.dumps(sucici)}),
+ Paramtest(param_cls=p13n.GfmSuciRi, val='123',
+ expect_clean_val='123',
+ expect_val={'GFM-5G-SUCI-RI': '123'}),
+ Paramtest(param_cls=p13n.GfmSuciRi, val='0',
+ expect_clean_val='0',
+ expect_val={'GFM-5G-SUCI-RI': '0'}),
+ Paramtest(param_cls=p13n.GfmSuciRi, val='9999',
+ expect_clean_val='9999',
+ expect_val={'GFM-5G-SUCI-RI': '9999'}),
+
+ Paramtest(param_cls=p13n.GfmSuciCalcInfoUe,
+ val=json.dumps(sucici),
+ expect_clean_val=sucici,
+ expect_val={'GFM-5G-SUCI-CalcInfo-UE': json.dumps(sucici)}),
+
])
Paramtest.iff_present_default = False
diff --git a/tests/unittests/xo/test_configurable_parameters b/tests/unittests/xo/test_configurable_parameters
index e6b87aa..422a347 100644
--- a/tests/unittests/xo/test_configurable_parameters
+++ b/tests/unittests/xo/test_configurable_parameters
@@ -289,6 +289,26 @@
previous value: []
skipping, param not in template.
+ok: SAIP2.1_gfmsuci.der GfmSuciRi(val='123':str)
+ clean_val='123':str
+ previous value: ['0']
+ read_back_val={'GFM-5G-SUCI-RI': '123'}:{hexstr}
+
+ok: SAIP2.1_gfmsuci.der GfmSuciRi(val='0':str)
+ clean_val='0':str
+ previous value: ['0']
+ read_back_val={'GFM-5G-SUCI-RI': '0'}:{hexstr}
+
+ok: SAIP2.1_gfmsuci.der GfmSuciRi(val='9999':str)
+ clean_val='9999':str
+ previous value: ['0']
+ read_back_val={'GFM-5G-SUCI-RI': '9999'}:{hexstr}
+
+ok: SAIP2.1_gfmsuci.der GfmSuciCalcInfoUe(val='{"prot_scheme_id_list": [{"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}], "hnet_pubkey_list": [{"hnet_pubkey_identifier": 27, "hnet_pubkey": "0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650"}]}':str)
+ clean_val={'prot_scheme_id_list': [{'priority': 0, 'identifier': 2, 'key_index': 1}, {'priority': 1, 'identifier': 1, 'key_index': 2}], 'hnet_pubkey_list': [{'hnet_pubkey_identifier': 27, 'hnet_pubkey': '0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4'}, {'hnet_pubkey_identifier': 30, 'hnet_pubkey': '5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650'}]}:{list, list}
+ previous value: ['{}']
+ read_back_val={'GFM-5G-SUCI-CalcInfo-UE': '{"prot_scheme_id_list": [{"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}], "hnet_pubkey_list": [{"hnet_pubkey_identifier": 27, "hnet_pubkey": "0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650"}]}'}:{str}
+
ok: SAIP2.1_gfmsuci.der SdKeyScp02Kvn20AesDek(val='01020304050607080910111213141516':str)
clean_val=b'\x01\x02\x03\x04\x05\x06\x07\x08\t\x10\x11\x12\x13\x14\x15\x16':bytes
previous value: []
@@ -1239,6 +1259,26 @@
previous value: []
skipping, param not in template.
+skip: TS48v5_SAIP2.1A_NoBERTLV.der GfmSuciRi(val='123':str)
+ clean_val='123':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.1A_NoBERTLV.der GfmSuciRi(val='0':str)
+ clean_val='0':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.1A_NoBERTLV.der GfmSuciRi(val='9999':str)
+ clean_val='9999':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.1A_NoBERTLV.der GfmSuciCalcInfoUe(val='{"prot_scheme_id_list": [{"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}], "hnet_pubkey_list": [{"hnet_pubkey_identifier": 27, "hnet_pubkey": "0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650"}]}':str)
+ clean_val={'prot_scheme_id_list': [{'priority': 0, 'identifier': 2, 'key_index': 1}, {'priority': 1, 'identifier': 1, 'key_index': 2}], 'hnet_pubkey_list': [{'hnet_pubkey_identifier': 27, 'hnet_pubkey': '0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4'}, {'hnet_pubkey_identifier': 30, 'hnet_pubkey': '5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650'}]}:{list, list}
+ previous value: []
+ skipping, param not in template.
+
ok: TS48v5_SAIP2.1A_NoBERTLV.der SdKeyScp02Kvn20AesDek(val='01020304050607080910111213141516':str)
clean_val=b'\x01\x02\x03\x04\x05\x06\x07\x08\t\x10\x11\x12\x13\x14\x15\x16':bytes
previous value: []
@@ -2189,6 +2229,26 @@
previous value: ['{"prot_scheme_id_list": [{"priority": 0, "identifier": 0, "key_index": 0}], "hnet_pubkey_list": []}']
read_back_val={'5G-SUCI-CalcInfo-USIM': '{"prot_scheme_id_list": [{"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}], "hnet_pubkey_list": [{"hnet_pubkey_identifier": 27, "hnet_pubkey": "0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650"}]}'}:{str}
+skip: TS48v5_SAIP2.3_BERTLV_SUCI.der GfmSuciRi(val='123':str)
+ clean_val='123':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.3_BERTLV_SUCI.der GfmSuciRi(val='0':str)
+ clean_val='0':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.3_BERTLV_SUCI.der GfmSuciRi(val='9999':str)
+ clean_val='9999':str
+ previous value: []
+ skipping, param not in template.
+
+skip: TS48v5_SAIP2.3_BERTLV_SUCI.der GfmSuciCalcInfoUe(val='{"prot_scheme_id_list": [{"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}], "hnet_pubkey_list": [{"hnet_pubkey_identifier": 27, "hnet_pubkey": "0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650"}]}':str)
+ clean_val={'prot_scheme_id_list': [{'priority': 0, 'identifier': 2, 'key_index': 1}, {'priority': 1, 'identifier': 1, 'key_index': 2}], 'hnet_pubkey_list': [{'hnet_pubkey_identifier': 27, 'hnet_pubkey': '0472da71976234ce833a6907425867b82e074d44ef907dfb4b3e21c1c2256ebcd15a7ded52fcbb097a4ed250e036c7b9c8c7004c4eedc4f068cd7bf8d3f900e3b4'}, {'hnet_pubkey_identifier': 30, 'hnet_pubkey': '5a8d38864820197c3394b92613b20b91633cbd897119273bf8e4a6f4eec0a650'}]}:{list, list}
+ previous value: []
+ skipping, param not in template.
+
ok: TS48v5_SAIP2.3_BERTLV_SUCI.der SdKeyScp02Kvn20AesDek(val='01020304050607080910111213141516':str)
clean_val=b'\x01\x02\x03\x04\x05\x06\x07\x08\t\x10\x11\x12\x13\x14\x15\x16':bytes
previous value: []
To view, visit change 43869. To unsubscribe, or for help writing mail filters, visit settings.