forked from public/pysim
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cc6ffb931 | |||
| 6597126847 | |||
| c899326226 | |||
| 30fb30f1e4 | |||
| 3344fd8c58 | |||
| 657afaa0d4 | |||
| 1d44cfd127 | |||
| 6ef99d43c7 | |||
| 8b91249781 | |||
| cc3f99b472 | |||
| d8c3d55c20 | |||
| 8333d6a340 | |||
| c28abecf8c | |||
| 5fac39bb51 | |||
| 61357d223e | |||
| 66acb109ab | |||
| 0c0a395f00 | |||
| ab07954999 | |||
| 907c29735e | |||
| e6aef652b0 | |||
| 09fbfdc39e | |||
| 6d0b3f8b85 | |||
| e0ac64d501 | |||
| f66b6fcc5b | |||
| 6f04e5e400 | |||
| 3470a3e062 | |||
| 696da34e81 | |||
| 42b8a70085 | |||
| 61264e32b8 | |||
| 659cb5d6c4 | |||
| c20b979875 | |||
| ddb5e3168e | |||
| ebeb00b48d | |||
| 69a74eb930 | |||
| c00813f66f | |||
| 0b8b59e74f | |||
| 09f2e50bd6 | |||
| c712ecbb0e | |||
| 69218d135f | |||
| 405ccabafd | |||
| 86fecce1db | |||
| a24dd6ac4a | |||
| 350aa9e01f | |||
| 167783ef0a | |||
| 5dbe1d3360 | |||
| ed5c032f76 | |||
| f677a99471 | |||
| 566a578a63 | |||
| 475d98dcea | |||
| a0ceb319e4 | |||
| f091d53cff | |||
| f45fb769e1 | |||
| e293227ead | |||
| a3a124acea | |||
| f4dd30dcf4 | |||
| 8c58834697 | |||
| 9325a04952 | |||
| 2128cd3c5b |
+14
-20
@@ -10,11 +10,6 @@
|
||||
|
||||
export PYTHONUNBUFFERED=1
|
||||
|
||||
setup_venv() {
|
||||
virtualenv -p python3 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
}
|
||||
|
||||
if [ ! -d "./tests/" ] ; then
|
||||
echo "###############################################"
|
||||
echo "Please call from pySim-prog top directory"
|
||||
@@ -28,7 +23,8 @@ fi
|
||||
|
||||
case "$JOB_TYPE" in
|
||||
"test")
|
||||
setup_venv
|
||||
virtualenv -p python3 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
pip install pyshark
|
||||
@@ -36,27 +32,23 @@ case "$JOB_TYPE" in
|
||||
# Execute automatically discovered unit tests first
|
||||
python -m unittest discover -v -s tests/unittests
|
||||
|
||||
# Run pySim-trace test
|
||||
tests/pySim-trace_test/pySim-trace_test.sh
|
||||
;;
|
||||
"card-test") # tests requiring physical cards
|
||||
setup_venv
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Run pySim-prog integration tests
|
||||
# Run pySim-prog integration tests (requires physical cards)
|
||||
cd tests/pySim-prog_test/
|
||||
./pySim-prog_test.sh
|
||||
./pySim-prog_test.sh
|
||||
cd ../../
|
||||
|
||||
# Run pySim-shell integration tests
|
||||
# Run pySim-trace test
|
||||
tests/pySim-trace_test/pySim-trace_test.sh
|
||||
|
||||
# Run pySim-shell integration tests (requires physical cards)
|
||||
python3 -m unittest discover -v -s ./tests/pySim-shell_test/
|
||||
|
||||
# Run pySim-smpp2sim test
|
||||
tests/pySim-smpp2sim_test/pySim-smpp2sim_test.sh
|
||||
;;
|
||||
"distcheck")
|
||||
setup_venv
|
||||
virtualenv -p python3 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
|
||||
pip install .
|
||||
pip install pyshark
|
||||
@@ -69,7 +61,8 @@ case "$JOB_TYPE" in
|
||||
# Print pylint version
|
||||
pip3 freeze | grep pylint
|
||||
|
||||
setup_venv
|
||||
virtualenv -p python3 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
|
||||
pip install .
|
||||
|
||||
@@ -87,7 +80,8 @@ case "$JOB_TYPE" in
|
||||
contrib/*.py
|
||||
;;
|
||||
"docs")
|
||||
setup_venv
|
||||
virtualenv -p python3 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from pySim import ts_102_222
|
||||
from pySim.utils import dec_imsi
|
||||
from pySim.ts_102_221 import FileDescriptor
|
||||
from pySim.filesystem import CardADF, Path
|
||||
from pySim.ts_31_102 import ADF_USIM, EF_UST, EF_SUCI_Calc_Info
|
||||
from pySim.ts_31_102 import ADF_USIM
|
||||
from pySim.ts_31_103 import ADF_ISIM
|
||||
from pySim.esim import compile_asn1_subdir
|
||||
from pySim.esim.saip import templates
|
||||
@@ -1517,11 +1517,8 @@ class ProfileElementHeader(ProfileElement):
|
||||
def mandatory_service_add(self, service_name):
|
||||
self.decoded['eUICC-Mandatory-services'][service_name] = None
|
||||
|
||||
def mandatory_service_present(self, service_name):
|
||||
return service_name in self.decoded['eUICC-Mandatory-services'].keys()
|
||||
|
||||
def mandatory_service_remove(self, service_name):
|
||||
if self.mandatory_service_present(service_name):
|
||||
if service_name in self.decoded['eUICC-Mandatory-services'].keys():
|
||||
del self.decoded['eUICC-Mandatory-services'][service_name]
|
||||
else:
|
||||
raise ValueError("service not in eUICC-Mandatory-services list, cannot remove")
|
||||
@@ -1729,64 +1726,12 @@ class ProfileElementSequence:
|
||||
if 'BT' in ftype_list:
|
||||
svc_set.add('ber-tlv')
|
||||
# FIXME:dfLinked files (scan all files, check for non-empty Fcp.linkPath presence of DFs)
|
||||
|
||||
# 5G:
|
||||
# - When SUCI is:
|
||||
# - enabled (EF.UST 124 = true)
|
||||
# AND
|
||||
# - calculated in the USIM (EF.UST 125 = true),
|
||||
# then eUICC-Mandatory-services needs 'get-identity'.
|
||||
# - 'get-identity' implies that the eUICC must support ONE OF profile-A OR profile-B.
|
||||
# So, when SUCI-CalcInfo for USIM in DF.SAIP contains both key types,
|
||||
# then no profile-A or B services need to be requested explicitly.
|
||||
# - When the SUCI-CalcInfo for USIM (DF.SAIP) contains ONLY a key of profile-A ("identifier": 1),
|
||||
# then eUICC-Mandatory-services needs 'profile-a-x25519'.
|
||||
# - Same: ONLY profile-B ("identifier": 2) needs 'profile-b-p256'.
|
||||
# - (When SUCI is calculated in the UE, then the eUICC does not need to provide any of these services.)
|
||||
suci_in_usim_enabled = False
|
||||
try:
|
||||
f_ust = self.get_pe_for_type("usim").files["ef-ust"]
|
||||
ust = EF_UST().decode_bin(f_ust.body)
|
||||
suci_in_usim_enabled = ust[124]['activated'] and ust[125]['activated']
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
if suci_in_usim_enabled:
|
||||
svc_set.add('get-identity')
|
||||
# now check for profile-a and profile-b
|
||||
suci_calcinfo_has_profile_a = False
|
||||
suci_calcinfo_has_profile_b = False
|
||||
try:
|
||||
f_sucici = self.get_pe_for_type("df-saip").files["ef-suci-calc-info-usim"]
|
||||
sucici = EF_SUCI_Calc_Info().decode_bin(f_sucici.body) or {}
|
||||
for prot_scheme in sucici['prot_scheme_id_list']:
|
||||
if not isinstance(prot_scheme, dict):
|
||||
continue
|
||||
ps_id = prot_scheme["identifier"]
|
||||
if ps_id == 1:
|
||||
suci_calcinfo_has_profile_a = True
|
||||
elif ps_id == 2:
|
||||
suci_calcinfo_has_profile_b = True
|
||||
except (KeyError, AttributeError):
|
||||
pass
|
||||
if suci_calcinfo_has_profile_a and suci_calcinfo_has_profile_b:
|
||||
# 'get-identity' implies that the eUICC supports one of the above. Do not require a specific one.
|
||||
pass
|
||||
elif suci_calcinfo_has_profile_a:
|
||||
# The profile has only a profile-A key, so require that
|
||||
svc_set.add('profile-a-x25519')
|
||||
elif suci_calcinfo_has_profile_b:
|
||||
# The profile has only a profile-B key, so require that
|
||||
svc_set.add('profile-b-p256')
|
||||
|
||||
# TODO: 5G related bits (derive from EF.UST or file presence?)
|
||||
hdr_pe = self.get_pe_for_type('header')
|
||||
# patch in the 'manual' services from the existing list:
|
||||
old_svc_set = set()
|
||||
for old_svc in hdr_pe.decoded['eUICC-Mandatory-services'].keys():
|
||||
if old_svc in manual_services:
|
||||
old_svc_set.add(old_svc)
|
||||
logger.debug(f"{svc_set=} + {old_svc_set=}")
|
||||
svc_set = svc_set.union(old_svc_set)
|
||||
logger.debug(f"{svc_set=}")
|
||||
svc_set.add(old_svc)
|
||||
hdr_pe.decoded['eUICC-Mandatory-services'] = {x: None for x in svc_set}
|
||||
|
||||
def rebuild_mandatory_gfstelist(self):
|
||||
|
||||
@@ -126,8 +126,6 @@ class BatchPersonalization:
|
||||
logger.error('during %s: %r', _func_(), e)
|
||||
raise ValueError(f'{p.param.name} fed by {p.src.name}: {e!r}') from e
|
||||
|
||||
pes.rebuild_mandatory_services()
|
||||
|
||||
yield pes
|
||||
|
||||
|
||||
|
||||
@@ -27,19 +27,13 @@ from construct.core import StreamError
|
||||
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, DF_USIM_5GS
|
||||
from pySim.ts_31_102 import EF_AD, EF_UST, EF_Routing_Indicator, EF_SUCI_Calc_Info
|
||||
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
|
||||
|
||||
# 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))
|
||||
|
||||
@@ -1163,7 +1157,7 @@ class MilenageRotationConstants(BinaryParam, AlgoConfig):
|
||||
|
||||
class MilenageXoringConstants(BinaryParam, AlgoConfig):
|
||||
"""XOR-ing constants c1,c2,c3,c4,c5 of Milenage, 128bit each. See 3GPP TS 35.206 Sections 2.3 + 5.3.
|
||||
Provided as octet-string concatenation of all 5 constants. The default value by 3GPP is the concatenation
|
||||
Provided as octet-string concatenation of all 5 constants. The default value by 3GPP is the concetenation
|
||||
of::
|
||||
|
||||
00000000000000000000000000000000
|
||||
@@ -1350,36 +1344,13 @@ class SuciCalcInfoParameter(ConfigurableParameter):
|
||||
if not f_sucici:
|
||||
continue
|
||||
ef_sucici = EF_SUCI_Calc_Info()
|
||||
body = ef_sucici.encode_bin(val)
|
||||
|
||||
# 0xff pad up to the existing file size, so that the underlying template doesn't come through
|
||||
is_size = f_sucici.file_size
|
||||
pad_n = is_size - len(body)
|
||||
if pad_n > 0:
|
||||
body = body + b'\xff' * pad_n
|
||||
|
||||
f_sucici.body = body
|
||||
f_sucici.body = ef_sucici.encode_bin(val)
|
||||
pe.file2pe(f_sucici)
|
||||
|
||||
@classmethod
|
||||
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):
|
||||
@@ -1389,8 +1360,15 @@ class SuciCalcInfoParameter(ConfigurableParameter):
|
||||
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)
|
||||
sucici = cls.normalize_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
|
||||
|
||||
yield { cls.name: json.dumps(sucici) }
|
||||
|
||||
@@ -1407,195 +1385,3 @@ 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
|
||||
|
||||
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
|
||||
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'
|
||||
|
||||
+1
-31
@@ -251,16 +251,6 @@ class EF_SMSP(LinFixedEF):
|
||||
"numbering_plan_id": "isdn_e164" },
|
||||
"call_number": "4915790109999" },
|
||||
"tp_pid": b"\x00", "tp_dcs": b"\x00", "tp_vp_minutes": 4320 } ),
|
||||
( 'e1ffffffffffffffffffffffff0891945197109099f9ffffff0000a9',
|
||||
{ "alpha_id": "", "parameter_indicators": { "tp_dest_addr": False, "tp_sc_addr": True,
|
||||
"tp_pid": True, "tp_dcs": True, "tp_vp": True },
|
||||
"tp_dest_addr": { "length": 255, "ton_npi": { "ext": True, "type_of_number": "reserved_for_extension",
|
||||
"numbering_plan_id": "reserved_for_extension" },
|
||||
"call_number": "" },
|
||||
"tp_sc_addr": { "length": 8, "ton_npi": { "ext": True, "type_of_number": "international",
|
||||
"numbering_plan_id": "isdn_e164" },
|
||||
"call_number": "4915790109999" },
|
||||
"tp_pid": b"\x00", "tp_dcs": b"\x00", "tp_vp_minutes": 4320 } ),
|
||||
( '454e6574776f726b73fffffffffffffff1ffffffffffffffffffffffffffffffffffffffffffffffff0000a7',
|
||||
{ "alpha_id": "ENetworks", "parameter_indicators": { "tp_dest_addr": False, "tp_sc_addr": True,
|
||||
"tp_pid": True, "tp_dcs": True, "tp_vp": False },
|
||||
@@ -341,8 +331,7 @@ class EF_SMSP(LinFixedEF):
|
||||
'ton_npi'/TonNpi, 'call_number'/PaddedBcdAdapter(Rpad(Bytes(10))))
|
||||
DestAddr = Struct('length'/Rebuild(Int8ub, lambda ctx: EF_SMSP.dest_addr_len(ctx)),
|
||||
'ton_npi'/TonNpi, 'call_number'/PaddedBcdAdapter(Rpad(Bytes(10))))
|
||||
# (see comment below)
|
||||
self._construct = Struct('alpha_id'/GsmOrUcs2Adapter(Rpad(Bytes(this._.total_len-28))),
|
||||
self._construct = Struct('alpha_id'/COptional(GsmOrUcs2Adapter(Rpad(Bytes(this._.total_len-28)))),
|
||||
'parameter_indicators'/InvertAdapter(BitStruct(
|
||||
Const(7, BitsInteger(3)),
|
||||
'tp_vp'/Flag,
|
||||
@@ -356,25 +345,6 @@ class EF_SMSP(LinFixedEF):
|
||||
'tp_dcs'/Bytes(1),
|
||||
'tp_vp_minutes'/EF_SMSP.ValidityPeriodAdapter(Byte))
|
||||
|
||||
# Ensure 'alpha_id' is always present
|
||||
def encode_record_hex(self, abstract_data: dict, record_nr: int, total_len: int = None) -> str:
|
||||
# Problem: TS 51.011 Section 10.5.6 describes the 'alpha_id' field as optional. However, this is only true
|
||||
# at the time when the record length of the file is set up in the file system. A card manufacturer may decide
|
||||
# to remove the field by setting the record length to 28. Likewise, the card manaufacturer may also decide to
|
||||
# set the field to a distinct length by setting the record length to a value greater than 28 (e.g. 14 bytes
|
||||
# 'alpha_id' + 28 bytes). Due to the fixed nature of the record length, this eventually means that in practice
|
||||
# 'alpha_id' is a mandatory field with a fixed length.
|
||||
#
|
||||
# Due to the problematic specification of 'alpha_id' as a pseudo-optional field at the beginning of a
|
||||
# fixed-size memory, the construct definition in self._construct has been incorrectly implemented and the field
|
||||
# has been marked as COptional. We may correct the problem by removing COptional. But to maintain compatibility,
|
||||
# we then have to ensure that in case the field is not provided (None), it is set to an empty string ('').
|
||||
#
|
||||
# See also ts_31_102.py, class EF_OCI for a correct example.
|
||||
if abstract_data['alpha_id'] is None:
|
||||
abstract_data['alpha_id'] = ''
|
||||
return super().encode_record_hex(abstract_data, record_nr, total_len)
|
||||
|
||||
# TS 51.011 Section 10.5.7
|
||||
class EF_SMSS(TransparentEF):
|
||||
class MemCapAdapter(Adapter):
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Utility to verify the functionality of pySim-smpp2sim.py
|
||||
# Utility to verify the functionality of pySim-trace.py
|
||||
#
|
||||
# (C) 2026 by sysmocom - s.f.m.c. GmbH
|
||||
# All Rights Reserved
|
||||
|
||||
@@ -54,7 +54,8 @@ class ConfigurableParameterTest(unittest.TestCase):
|
||||
def test_parameters(self):
|
||||
|
||||
upp_fnames = (
|
||||
'SAIP2.1_gfmsuci.der',
|
||||
'TS48v5_SAIP2.1A_NoBERTLV.der',
|
||||
'TS48v5_SAIP2.3_BERTLV_SUCI.der',
|
||||
'TS48v5_SAIP2.1B_NoBERTLV.der',
|
||||
'TS48v5_SAIP2.3_NoBERTLV.der',
|
||||
)
|
||||
@@ -280,33 +281,6 @@ 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
|
||||
@@ -349,30 +323,10 @@ class ConfigurableParameterTest(unittest.TestCase):
|
||||
expect_clean_val='9999',
|
||||
expect_val={'5G-SUCI-RI': '9999'}),
|
||||
|
||||
Paramtest(param_cls=p13n.SuciCalcInfoUe,
|
||||
Paramtest(param_cls=p13n.SuciCalcInfo,
|
||||
val=json.dumps(sucici),
|
||||
expect_clean_val=sucici,
|
||||
expect_val={'5G-SUCI-CalcInfo-UE': json.dumps(sucici)}),
|
||||
|
||||
Paramtest(param_cls=p13n.SuciCalcInfoUsim,
|
||||
val=json.dumps(sucici),
|
||||
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)}),
|
||||
expect_val={'5G-SUCI-CalcInfo': json.dumps(sucici)}),
|
||||
|
||||
])
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user