docs: Better python doc-strings for better pySim.esim manual

Change-Id: I7be6264c665a2a25105681bb5e72d0f6715bbef8
This commit is contained in:
Harald Welte
2025-05-06 22:01:14 +02:00
parent 89070a7c67
commit e37cdbcd3e
10 changed files with 87 additions and 50 deletions

View File

@@ -61,8 +61,8 @@ def compile_asn1_subdir(subdir_name:str, codec='der'):
return asn1tools.compile_string(asn_txt, codec=codec) return asn1tools.compile_string(asn_txt, codec=codec)
# SGP.22 section 4.1 Activation Code
class ActivationCode: class ActivationCode:
"""SGP.22 section 4.1 Activation Code"""
def __init__(self, hostname:str, token:str, oid: Optional[str] = None, cc_required: Optional[bool] = False): def __init__(self, hostname:str, token:str, oid: Optional[str] = None, cc_required: Optional[bool] = False):
if '$' in hostname: if '$' in hostname:
raise ValueError('$ sign not permitted in hostname') raise ValueError('$ sign not permitted in hostname')
@@ -78,6 +78,7 @@ class ActivationCode:
@staticmethod @staticmethod
def decode_str(ac: str) -> dict: def decode_str(ac: str) -> dict:
"""decode an activation code from its string representation."""
if ac[0] != '1': if ac[0] != '1':
raise ValueError("Unsupported AC_Format '%s'!" % ac[0]) raise ValueError("Unsupported AC_Format '%s'!" % ac[0])
ac_elements = ac.split('$') ac_elements = ac.split('$')

View File

@@ -1,11 +1,9 @@
# Early proof-of-concept implementation of """Implementation of GSMA eSIM RSP (Remote SIM Provisioning BSP (BPP Protection Protocol),
# GSMA eSIM RSP (Remote SIM Provisioning BSP (BPP Protection Protocol), where BPP is the Bound Profile Package. So the full expansion is the
# where BPP is the Bound Profile Package. So the full expansion is the "GSMA eSIM Remote SIM Provisioning Bound Profile Packate Protection Protocol"
# "GSMA eSIM Remote SIM Provisioning Bound Profile Packate Protection Protocol"
# Originally (SGP.22 v2.x) this was called SCP03t, but it has since been renamed to BSP."""
# Originally (SGP.22 v2.x) this was called SCP03t, but it has since been
# renamed to BSP.
#
# (C) 2023 by Harald Welte <laforge@osmocom.org> # (C) 2023 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -45,6 +43,7 @@ logger.addHandler(logging.NullHandler())
MAX_SEGMENT_SIZE = 1020 MAX_SEGMENT_SIZE = 1020
class BspAlgo(abc.ABC): class BspAlgo(abc.ABC):
"""Base class representing a cryptographic algorithm within the BSP (BPP Security Protocol)."""
blocksize: int blocksize: int
def _get_padding(self, in_len: int, multiple: int, padding: int = 0) -> bytes: def _get_padding(self, in_len: int, multiple: int, padding: int = 0) -> bytes:
@@ -62,6 +61,7 @@ class BspAlgo(abc.ABC):
return self.__class__.__name__ return self.__class__.__name__
class BspAlgoCrypt(BspAlgo, abc.ABC): class BspAlgoCrypt(BspAlgo, abc.ABC):
"""Base class representing an encryption/decryption algorithm within the BSP (BPP Security Protocol)."""
def __init__(self, s_enc: bytes): def __init__(self, s_enc: bytes):
self.s_enc = s_enc self.s_enc = s_enc
@@ -93,6 +93,7 @@ class BspAlgoCrypt(BspAlgo, abc.ABC):
"""Actual implementation, to be implemented by derived class.""" """Actual implementation, to be implemented by derived class."""
class BspAlgoCryptAES128(BspAlgoCrypt): class BspAlgoCryptAES128(BspAlgoCrypt):
"""AES-CBC-128 implementation of the BPP Security Protocol for GSMA SGP.22 eSIM."""
name = 'AES-CBC-128' name = 'AES-CBC-128'
blocksize = 16 blocksize = 16
@@ -133,6 +134,7 @@ class BspAlgoCryptAES128(BspAlgoCrypt):
class BspAlgoMac(BspAlgo, abc.ABC): class BspAlgoMac(BspAlgo, abc.ABC):
"""Base class representing a message authentication code algorithm within the BSP (BPP Security Protocol)."""
l_mac = 0 # must be overridden by derived class l_mac = 0 # must be overridden by derived class
def __init__(self, s_mac: bytes, initial_mac_chaining_value: bytes): def __init__(self, s_mac: bytes, initial_mac_chaining_value: bytes):
@@ -167,6 +169,7 @@ class BspAlgoMac(BspAlgo, abc.ABC):
"""To be implemented by algorithm specific derived class.""" """To be implemented by algorithm specific derived class."""
class BspAlgoMacAES128(BspAlgoMac): class BspAlgoMacAES128(BspAlgoMac):
"""AES-CMAC-128 implementation of the BPP Security Protocol for GSMA SGP.22 eSIM."""
name = 'AES-CMAC-128' name = 'AES-CMAC-128'
l_mac = 8 l_mac = 8

View File

@@ -1,6 +1,5 @@
# Implementation of GSMA eSIM RSP (Remote SIM Provisioning) ES8+ """Implementation of GSMA eSIM RSP (Remote SIM Provisioning) ES8+ as per SGP22 v3.0 Section 5.5"""
# as per SGP22 v3.0 Section 5.5
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify

View File

@@ -1,6 +1,5 @@
# Implementation of GSMA eSIM RSP (Remote SIM Provisioning) """Implementation of GSMA eSIM RSP (Remote SIM Provisioning) as per SGP22 v3.0"""
# as per SGP22 v3.0
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify

View File

@@ -1,5 +1,5 @@
# Implementation of SimAlliance/TCA Interoperable Profile handling """Implementation of SimAlliance/TCA Interoperable Profile handling"""
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -45,7 +45,7 @@ asn1 = compile_asn1_subdir('saip')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Naa: class Naa:
"""A class defining a Network Access Application (NAA).""" """A class defining a Network Access Application (NAA)"""
name = None name = None
# AID prefix, as used for ADF and EF.DIR # AID prefix, as used for ADF and EF.DIR
aid = None aid = None
@@ -61,6 +61,7 @@ class Naa:
return 'adf-' + cls.mandatory_services[0] return 'adf-' + cls.mandatory_services[0]
class NaaCsim(Naa): class NaaCsim(Naa):
"""A class representing the CSIM (CDMA) Network Access Application (NAA)"""
name = "csim" name = "csim"
aid = h2b("") aid = h2b("")
mandatory_services = ["csim"] mandatory_services = ["csim"]
@@ -68,6 +69,7 @@ class NaaCsim(Naa):
templates = [oid.ADF_CSIM_by_default, oid.ADF_CSIMopt_not_by_default] templates = [oid.ADF_CSIM_by_default, oid.ADF_CSIMopt_not_by_default]
class NaaUsim(Naa): class NaaUsim(Naa):
"""A class representing the USIM Network Access Application (NAA)"""
name = "usim" name = "usim"
aid = h2b("a0000000871002") aid = h2b("a0000000871002")
mandatory_services = ["usim"] mandatory_services = ["usim"]
@@ -80,6 +82,7 @@ class NaaUsim(Naa):
adf = ADF_USIM() adf = ADF_USIM()
class NaaIsim(Naa): class NaaIsim(Naa):
"""A class representing the ISIM Network Access Application (NAA)"""
name = "isim" name = "isim"
aid = h2b("a0000000871004") aid = h2b("a0000000871004")
mandatory_services = ["isim"] mandatory_services = ["isim"]
@@ -750,6 +753,7 @@ class ProfileElementGFM(ProfileElement):
class ProfileElementMF(FsProfileElement): class ProfileElementMF(FsProfileElement):
"""Class representing the ProfileElement for the MF (Master File)"""
type = 'mf' type = 'mf'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -763,6 +767,7 @@ class ProfileElementMF(FsProfileElement):
# TODO: resize EF.DIR? # TODO: resize EF.DIR?
class ProfileElementPuk(ProfileElement): class ProfileElementPuk(ProfileElement):
"""Class representing the ProfileElement for a PUK (PIN Unblocking Code)"""
type = 'pukCodes' type = 'pukCodes'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -793,6 +798,7 @@ class ProfileElementPuk(ProfileElement):
class ProfileElementPin(ProfileElement): class ProfileElementPin(ProfileElement):
"""Class representing the ProfileElement for a PIN (Personal Identification Number)"""
type = 'pinCodes' type = 'pinCodes'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -829,6 +835,7 @@ class ProfileElementPin(ProfileElement):
class ProfileElementTelecom(FsProfileElement): class ProfileElementTelecom(FsProfileElement):
"""Class representing the ProfileElement for DF.TELECOM"""
type = 'telecom' type = 'telecom'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -841,6 +848,7 @@ class ProfileElementTelecom(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementCD(FsProfileElement): class ProfileElementCD(FsProfileElement):
"""Class representing the ProfileElement for DF.CD"""
type = 'cd' type = 'cd'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -853,6 +861,7 @@ class ProfileElementCD(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementPhonebook(FsProfileElement): class ProfileElementPhonebook(FsProfileElement):
"""Class representing the ProfileElement for DF.PHONEBOOK"""
type = 'phonebook' type = 'phonebook'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -865,6 +874,7 @@ class ProfileElementPhonebook(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementGsmAccess(FsProfileElement): class ProfileElementGsmAccess(FsProfileElement):
"""Class representing the ProfileElement for ADF.USIM/DF.GSM-ACCESS"""
type = 'gsm-access' type = 'gsm-access'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -877,6 +887,7 @@ class ProfileElementGsmAccess(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementDf5GS(FsProfileElement): class ProfileElementDf5GS(FsProfileElement):
"""Class representing the ProfileElement for ADF.USIM/DF.5GS"""
type = 'df-5gs' type = 'df-5gs'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -889,6 +900,7 @@ class ProfileElementDf5GS(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementEAP(FsProfileElement): class ProfileElementEAP(FsProfileElement):
"""Class representing the ProfileElement for DF.EAP"""
type = 'eap' type = 'eap'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -901,6 +913,7 @@ class ProfileElementEAP(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementDfSAIP(FsProfileElement): class ProfileElementDfSAIP(FsProfileElement):
"""Class representing the ProfileElement for DF.SAIP"""
type = 'df-saip' type = 'df-saip'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -913,6 +926,7 @@ class ProfileElementDfSAIP(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementDfSNPN(FsProfileElement): class ProfileElementDfSNPN(FsProfileElement):
"""Class representing the ProfileElement for DF.SNPN"""
type = 'df-snpn' type = 'df-snpn'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -925,6 +939,7 @@ class ProfileElementDfSNPN(FsProfileElement):
self.decoded[fname] = [] self.decoded[fname] = []
class ProfileElementDf5GProSe(FsProfileElement): class ProfileElementDf5GProSe(FsProfileElement):
"""Class representing the ProfileElement for DF.5GProSe"""
type = 'df-5gprose' type = 'df-5gprose'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -1218,6 +1233,7 @@ class ProfileElementApplication(ProfileElement):
class ProfileElementRFM(ProfileElement): class ProfileElementRFM(ProfileElement):
"""Class representing the ProfileElement for RFM (Remote File Management)."""
type = 'rfm' type = 'rfm'
def __init__(self, decoded: Optional[dict] = None, def __init__(self, decoded: Optional[dict] = None,
@@ -1243,6 +1259,7 @@ class ProfileElementRFM(ProfileElement):
} }
class ProfileElementUSIM(FsProfileElement): class ProfileElementUSIM(FsProfileElement):
"""Class representing the ProfileElement for ADF.USIM Mandatory Files"""
type = 'usim' type = 'usim'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -1265,6 +1282,7 @@ class ProfileElementUSIM(FsProfileElement):
return dec_imsi(b2h(f.body)) return dec_imsi(b2h(f.body))
class ProfileElementOptUSIM(FsProfileElement): class ProfileElementOptUSIM(FsProfileElement):
"""Class representing the ProfileElement for ADF.USIM Optional Files"""
type = 'opt-usim' type = 'opt-usim'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -1275,6 +1293,7 @@ class ProfileElementOptUSIM(FsProfileElement):
self.decoded['templateID'] = str(oid.ADF_USIMopt_not_by_default_v2) self.decoded['templateID'] = str(oid.ADF_USIMopt_not_by_default_v2)
class ProfileElementISIM(FsProfileElement): class ProfileElementISIM(FsProfileElement):
"""Class representing the ProfileElement for ADF.ISIM Mandatory Files"""
type = 'isim' type = 'isim'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -1291,6 +1310,7 @@ class ProfileElementISIM(FsProfileElement):
return b2h(self.decoded['adf-isim'][0][1]['dfName']) return b2h(self.decoded['adf-isim'][0][1]['dfName'])
class ProfileElementOptISIM(FsProfileElement): class ProfileElementOptISIM(FsProfileElement):
"""Class representing the ProfileElement for ADF.ISIM Optional Files"""
type = 'opt-isim' type = 'opt-isim'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
@@ -1302,6 +1322,7 @@ class ProfileElementOptISIM(FsProfileElement):
class ProfileElementAKA(ProfileElement): class ProfileElementAKA(ProfileElement):
"""Class representing the ProfileElement for Authentication and Key Agreement (AKA)."""
type = 'akaParameter' type = 'akaParameter'
# TODO: RES size for USIM test algorithm can be set to 32, 64 or 128 bits. This value was # TODO: RES size for USIM test algorithm can be set to 32, 64 or 128 bits. This value was
# previously limited to 128 bits. Recommendation: Avoid using RES size 32 or 64 in Profiles # previously limited to 128 bits. Recommendation: Avoid using RES size 32 or 64 in Profiles
@@ -1381,6 +1402,7 @@ class ProfileElementAKA(ProfileElement):
}) })
class ProfileElementHeader(ProfileElement): class ProfileElementHeader(ProfileElement):
"""Class representing the ProfileElement for the Header of the PE-Sequence."""
type = 'header' type = 'header'
def __init__(self, decoded: Optional[dict] = None, def __init__(self, decoded: Optional[dict] = None,
ver_major: Optional[int] = 2, ver_minor: Optional[int] = 3, ver_major: Optional[int] = 2, ver_minor: Optional[int] = 3,
@@ -1421,6 +1443,7 @@ class ProfileElementHeader(ProfileElement):
raise ValueError("service not in eUICC-Mandatory-services list, cannot remove") raise ValueError("service not in eUICC-Mandatory-services list, cannot remove")
class ProfileElementEnd(ProfileElement): class ProfileElementEnd(ProfileElement):
"""Class representing the ProfileElement for the End of the PE-Sequence."""
type = 'end' type = 'end'
def __init__(self, decoded: Optional[dict] = None, **kwargs): def __init__(self, decoded: Optional[dict] = None, **kwargs):
super().__init__(decoded, **kwargs) super().__init__(decoded, **kwargs)

View File

@@ -1,5 +1,5 @@
# Implementation of SimAlliance/TCA Interoperable Profile OIDs """Implementation of SimAlliance/TCA Interoperable Profile OIDs"""
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify

View File

@@ -1,5 +1,5 @@
# Implementation of SimAlliance/TCA Interoperable Profile handling """Implementation of Personalization of eSIM profiles in SimAlliance/TCA Interoperable Profile."""
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify

View File

@@ -1,5 +1,5 @@
# Implementation of SimAlliance/TCA Interoperable Profile Template handling """Implementation of SimAlliance/TCA Interoperable Profile Templates."""
#
# (C) 2024 by Harald Welte <laforge@osmocom.org> # (C) 2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -224,8 +224,8 @@ class ProfileTemplateRegistry:
# below are transcribed template definitions from "ANNEX A (Normative): File Structure Templates Definition" # below are transcribed template definitions from "ANNEX A (Normative): File Structure Templates Definition"
# of "Profile interoperability specification V3.3.1 Final" (unless other version explicitly specified). # of "Profile interoperability specification V3.3.1 Final" (unless other version explicitly specified).
# Section 9.2
class FilesAtMF(ProfileTemplate): class FilesAtMF(ProfileTemplate):
"""Files at MF as per Section 9.2"""
created_by_default = True created_by_default = True
oid = OID.MF oid = OID.MF
files = [ files = [
@@ -238,8 +238,8 @@ class FilesAtMF(ProfileTemplate):
] ]
# Section 9.3
class FilesCD(ProfileTemplate): class FilesCD(ProfileTemplate):
"""Files at DF.CD as per Section 9.3"""
created_by_default = False created_by_default = False
oid = OID.DF_CD oid = OID.DF_CD
files = [ files = [
@@ -287,8 +287,8 @@ for i in range(0x90, 0x98):
for i in range(0x98, 0xa0): for i in range(0x98, 0xa0):
df_pb_files.append(FileTemplate(0x4f00+i, 'EF.CCP1', 'LF', None, None, 5, None, 'FF...FF', False, ['nb_rec','size','sfi'], ppath=[0x5f3a])) df_pb_files.append(FileTemplate(0x4f00+i, 'EF.CCP1', 'LF', None, None, 5, None, 'FF...FF', False, ['nb_rec','size','sfi'], ppath=[0x5f3a]))
# Section 9.4 v2.3.1
class FilesTelecom(ProfileTemplate): class FilesTelecom(ProfileTemplate):
"""Files at DF.TELECOM as per Section 9.4 v2.3.1"""
created_by_default = False created_by_default = False
oid = OID.DF_TELECOM oid = OID.DF_TELECOM
base_path = Path('MF') base_path = Path('MF')
@@ -328,8 +328,8 @@ class FilesTelecom(ProfileTemplate):
] ]
# Section 9.4
class FilesTelecomV2(ProfileTemplate): class FilesTelecomV2(ProfileTemplate):
"""Files at DF.TELECOM as per Section 9.4"""
created_by_default = False created_by_default = False
oid = OID.DF_TELECOM_v2 oid = OID.DF_TELECOM_v2
base_path = Path('MF') base_path = Path('MF')
@@ -379,8 +379,8 @@ class FilesTelecomV2(ProfileTemplate):
] ]
# Section 9.5.1 v2.3.1
class FilesUsimMandatory(ProfileTemplate): class FilesUsimMandatory(ProfileTemplate):
"""Mandatory Files at ADF.USIM as per Section 9.5.1 v2.3.1"""
created_by_default = True created_by_default = True
oid = OID.ADF_USIM_by_default oid = OID.ADF_USIM_by_default
files = [ files = [
@@ -410,8 +410,8 @@ class FilesUsimMandatory(ProfileTemplate):
FileTemplate(0x6fe4, 'EF.EPSNSC', 'LF', 1, 80, 5, 0x18, 'FF...FF', False, ass_serv=[85], high_update=True), FileTemplate(0x6fe4, 'EF.EPSNSC', 'LF', 1, 80, 5, 0x18, 'FF...FF', False, ass_serv=[85], high_update=True),
] ]
# Section 9.5.1
class FilesUsimMandatoryV2(ProfileTemplate): class FilesUsimMandatoryV2(ProfileTemplate):
"""Mandatory Files at ADF.USIM as per Section 9.5.1"""
created_by_default = True created_by_default = True
oid = OID.ADF_USIM_by_default_v2 oid = OID.ADF_USIM_by_default_v2
files = [ files = [
@@ -442,8 +442,8 @@ class FilesUsimMandatoryV2(ProfileTemplate):
] ]
# Section 9.5.2 v2.3.1
class FilesUsimOptional(ProfileTemplate): class FilesUsimOptional(ProfileTemplate):
"""Optional Files at ADF.USIM as per Section 9.5.2 v2.3.1"""
created_by_default = False created_by_default = False
optional = True optional = True
oid = OID.ADF_USIMopt_not_by_default oid = OID.ADF_USIMopt_not_by_default
@@ -529,6 +529,7 @@ class FilesUsimOptional(ProfileTemplate):
# Section 9.5.2 # Section 9.5.2
class FilesUsimOptionalV2(ProfileTemplate): class FilesUsimOptionalV2(ProfileTemplate):
"""Optional Files at ADF.USIM as per Section 9.5.2"""
created_by_default = False created_by_default = False
optional = True optional = True
oid = OID.ADF_USIMopt_not_by_default_v2 oid = OID.ADF_USIMopt_not_by_default_v2
@@ -622,8 +623,8 @@ class FilesUsimOptionalV2(ProfileTemplate):
FileTemplate(0x6ffd, 'EF.MudMidCfgdata','BT', None, None,2, None, None, True, ['size'], ass_serv=[134]), FileTemplate(0x6ffd, 'EF.MudMidCfgdata','BT', None, None,2, None, None, True, ['size'], ass_serv=[134]),
] ]
# Section 9.5.2.3 v3.3.1
class FilesUsimOptionalV3(ProfileTemplate): class FilesUsimOptionalV3(ProfileTemplate):
"""Optional Files at ADF.USIM as per Section 9.5.2.3 v3.3.1"""
created_by_default = False created_by_default = False
optional = True optional = True
oid = OID.ADF_USIMopt_not_by_default_v3 oid = OID.ADF_USIMopt_not_by_default_v3
@@ -633,16 +634,16 @@ class FilesUsimOptionalV3(ProfileTemplate):
FileTemplate(0x6f01, 'EF.eAKA', 'TR', None, 1, 3, None, None, True, ['size'], ass_serv=[134]), FileTemplate(0x6f01, 'EF.eAKA', 'TR', None, 1, 3, None, None, True, ['size'], ass_serv=[134]),
] ]
# Section 9.5.3
class FilesUsimDfPhonebook(ProfileTemplate): class FilesUsimDfPhonebook(ProfileTemplate):
"""DF.PHONEBOOK Files at ADF.USIM as per Section 9.5.3"""
created_by_default = False created_by_default = False
oid = OID.DF_PHONEBOOK_ADF_USIM oid = OID.DF_PHONEBOOK_ADF_USIM
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
files = df_pb_files files = df_pb_files
# Section 9.5.4
class FilesUsimDfGsmAccess(ProfileTemplate): class FilesUsimDfGsmAccess(ProfileTemplate):
"""DF.GSM-ACCESS Files at ADF.USIM as per Section 9.5.4"""
created_by_default = False created_by_default = False
oid = OID.DF_GSM_ACCESS_ADF_USIM oid = OID.DF_GSM_ACCESS_ADF_USIM
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -656,8 +657,8 @@ class FilesUsimDfGsmAccess(ProfileTemplate):
] ]
# Section 9.5.11 v2.3.1
class FilesUsimDf5GS(ProfileTemplate): class FilesUsimDf5GS(ProfileTemplate):
"""DF.5GS Files at ADF.USIM as per Section 9.5.11 v2.3.1"""
created_by_default = False created_by_default = False
oid = OID.DF_5GS oid = OID.DF_5GS
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -677,8 +678,8 @@ class FilesUsimDf5GS(ProfileTemplate):
] ]
# Section 9.5.11.2
class FilesUsimDf5GSv2(ProfileTemplate): class FilesUsimDf5GSv2(ProfileTemplate):
"""DF.5GS Files at ADF.USIM as per Section 9.5.11.2"""
created_by_default = False created_by_default = False
oid = OID.DF_5GS_v2 oid = OID.DF_5GS_v2
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -700,8 +701,8 @@ class FilesUsimDf5GSv2(ProfileTemplate):
] ]
# Section 9.5.11.3
class FilesUsimDf5GSv3(ProfileTemplate): class FilesUsimDf5GSv3(ProfileTemplate):
"""DF.5GS Files at ADF.USIM as per Section 9.5.11.3"""
created_by_default = False created_by_default = False
oid = OID.DF_5GS_v3 oid = OID.DF_5GS_v3
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -724,8 +725,8 @@ class FilesUsimDf5GSv3(ProfileTemplate):
FileTemplate(0x4f0c, 'EF.TN3GPPSNN', 'TR', None, 1, 2, 0x0c, '00', False, ass_serv=[135]), FileTemplate(0x4f0c, 'EF.TN3GPPSNN', 'TR', None, 1, 2, 0x0c, '00', False, ass_serv=[135]),
] ]
# Section 9.5.11.4
class FilesUsimDf5GSv4(ProfileTemplate): class FilesUsimDf5GSv4(ProfileTemplate):
"""DF.5GS Files at ADF.USIM as per Section 9.5.11.4"""
created_by_default = False created_by_default = False
oid = OID.DF_5GS_v4 oid = OID.DF_5GS_v4
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -756,8 +757,8 @@ class FilesUsimDf5GSv4(ProfileTemplate):
] ]
# Section 9.5.12
class FilesUsimDfSaip(ProfileTemplate): class FilesUsimDfSaip(ProfileTemplate):
"""DF.SAIP Files at ADF.USIM as per Section 9.5.12"""
created_by_default = False created_by_default = False
oid = OID.DF_SAIP oid = OID.DF_SAIP
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -767,8 +768,8 @@ class FilesUsimDfSaip(ProfileTemplate):
FileTemplate(0x4f01, 'EF.SUCICalcInfo','TR', None, None, 3, None, 'FF...FF', False, ['size'], ass_serv=[125], pe_name='ef-suci-calc-info-usim'), FileTemplate(0x4f01, 'EF.SUCICalcInfo','TR', None, None, 3, None, 'FF...FF', False, ['size'], ass_serv=[125], pe_name='ef-suci-calc-info-usim'),
] ]
# Section 9.5.13
class FilesDfSnpn(ProfileTemplate): class FilesDfSnpn(ProfileTemplate):
"""DF.SNPN Files at ADF.USIM as per Section 9.5.13"""
created_by_default = False created_by_default = False
oid = OID.DF_SNPN oid = OID.DF_SNPN
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -778,8 +779,8 @@ class FilesDfSnpn(ProfileTemplate):
FileTemplate(0x4f01, 'EF.PWS_SNPN', 'TR', None, 1, 10, None, None, True, ass_serv=[143]), FileTemplate(0x4f01, 'EF.PWS_SNPN', 'TR', None, 1, 10, None, None, True, ass_serv=[143]),
] ]
# Section 9.5.14
class FilesDf5GProSe(ProfileTemplate): class FilesDf5GProSe(ProfileTemplate):
"""DF.ProSe Files at ADF.USIM as per Section 9.5.14"""
created_by_default = False created_by_default = False
oid = OID.DF_5GProSe oid = OID.DF_5GProSe
base_path = Path('ADF.USIM') base_path = Path('ADF.USIM')
@@ -794,8 +795,8 @@ class FilesDf5GProSe(ProfileTemplate):
FileTemplate(0x4f06, 'EF.5G_PROSE_UIR', 'TR', None, 32, 2, 0x06, None, True, ass_serv=[139,1005]), FileTemplate(0x4f06, 'EF.5G_PROSE_UIR', 'TR', None, 32, 2, 0x06, None, True, ass_serv=[139,1005]),
] ]
# Section 9.6.1
class FilesIsimMandatory(ProfileTemplate): class FilesIsimMandatory(ProfileTemplate):
"""Mandatory Files at ADF.ISIM as per Section 9.6.1"""
created_by_default = True created_by_default = True
oid = OID.ADF_ISIM_by_default oid = OID.ADF_ISIM_by_default
files = [ files = [
@@ -809,8 +810,8 @@ class FilesIsimMandatory(ProfileTemplate):
] ]
# Section 9.6.2 v2.3.1
class FilesIsimOptional(ProfileTemplate): class FilesIsimOptional(ProfileTemplate):
"""Optional Files at ADF.ISIM as per Section 9.6.2 of v2.3.1"""
created_by_default = False created_by_default = False
optional = True optional = True
oid = OID.ADF_ISIMopt_not_by_default oid = OID.ADF_ISIMopt_not_by_default
@@ -829,8 +830,8 @@ class FilesIsimOptional(ProfileTemplate):
] ]
# Section 9.6.2
class FilesIsimOptionalv2(ProfileTemplate): class FilesIsimOptionalv2(ProfileTemplate):
"""Optional Files at ADF.ISIM as per Section 9.6.2"""
created_by_default = False created_by_default = False
optional = True optional = True
oid = OID.ADF_ISIMopt_not_by_default_v2 oid = OID.ADF_ISIMopt_not_by_default_v2
@@ -857,8 +858,8 @@ class FilesIsimOptionalv2(ProfileTemplate):
# TODO: CSIM # TODO: CSIM
# Section 9.8
class FilesEap(ProfileTemplate): class FilesEap(ProfileTemplate):
"""Files at DF.EAP as per Section 9.8"""
created_by_default = False created_by_default = False
oid = OID.DF_EAP oid = OID.DF_EAP
files = [ files = [

View File

@@ -1,5 +1,5 @@
# Implementation of SimAlliance/TCA Interoperable Profile handling """Implementation of SimAlliance/TCA Interoperable Profile validation."""
#
# (C) 2023-2024 by Harald Welte <laforge@osmocom.org> # (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
@@ -19,16 +19,21 @@
from pySim.esim.saip import * from pySim.esim.saip import *
class ProfileError(Exception): class ProfileError(Exception):
"""Raised when a ProfileConstraintChecker finds an error in a file [structure]."""
pass pass
class ProfileConstraintChecker: class ProfileConstraintChecker:
"""Base class of a constraint checker for a ProfileElementSequence."""
def check(self, pes: ProfileElementSequence): def check(self, pes: ProfileElementSequence):
"""Execute all the check_* methods of the ProfileConstraintChecker against the given
ProfileElementSequence"""
for name in dir(self): for name in dir(self):
if name.startswith('check_'): if name.startswith('check_'):
method = getattr(self, name) method = getattr(self, name)
method(pes) method(pes)
class CheckBasicStructure(ProfileConstraintChecker): class CheckBasicStructure(ProfileConstraintChecker):
"""ProfileConstraintChecker for the basic profile structure constraints."""
def _is_after_if_exists(self, pes: ProfileElementSequence, opt:str, after:str): def _is_after_if_exists(self, pes: ProfileElementSequence, opt:str, after:str):
opt_pe = pes.get_pe_for_type(opt) opt_pe = pes.get_pe_for_type(opt)
if opt_pe: if opt_pe:
@@ -38,6 +43,7 @@ class CheckBasicStructure(ProfileConstraintChecker):
# FIXME: check order # FIXME: check order
def check_start_and_end(self, pes: ProfileElementSequence): def check_start_and_end(self, pes: ProfileElementSequence):
"""Check for mandatory header and end ProfileElements at the right position."""
if pes.pe_list[0].type != 'header': if pes.pe_list[0].type != 'header':
raise ProfileError('first element is not header') raise ProfileError('first element is not header')
if pes.pe_list[1].type != 'mf': if pes.pe_list[1].type != 'mf':
@@ -47,6 +53,7 @@ class CheckBasicStructure(ProfileConstraintChecker):
raise ProfileError('last element is not end') raise ProfileError('last element is not end')
def check_number_of_occurrence(self, pes: ProfileElementSequence): def check_number_of_occurrence(self, pes: ProfileElementSequence):
"""Check The number of occurrence of various ProfileElements."""
# check for invalid number of occurrences # check for invalid number of occurrences
if len(pes.get_pes_for_type('header')) != 1: if len(pes.get_pes_for_type('header')) != 1:
raise ProfileError('multiple ProfileHeader') raise ProfileError('multiple ProfileHeader')
@@ -60,6 +67,7 @@ class CheckBasicStructure(ProfileConstraintChecker):
raise ProfileError('multiple PE-%s' % tn.upper()) raise ProfileError('multiple PE-%s' % tn.upper())
def check_optional_ordering(self, pes: ProfileElementSequence): def check_optional_ordering(self, pes: ProfileElementSequence):
"""Check the ordering of optional PEs following the respective mandatory ones."""
# ordering and required depenencies # ordering and required depenencies
self._is_after_if_exists(pes,'opt-usim', 'usim') self._is_after_if_exists(pes,'opt-usim', 'usim')
self._is_after_if_exists(pes,'opt-isim', 'isim') self._is_after_if_exists(pes,'opt-isim', 'isim')
@@ -104,17 +112,21 @@ class CheckBasicStructure(ProfileConstraintChecker):
FileChoiceList = List[Tuple] FileChoiceList = List[Tuple]
class FileError(ProfileError): class FileError(ProfileError):
"""Raised when a FileConstraintChecker finds an error in a file [structure]."""
pass pass
class FileConstraintChecker: class FileConstraintChecker:
def check(self, l: FileChoiceList): def check(self, l: FileChoiceList):
"""Execute all the check_* methods of the FileConstraintChecker against the given FileChoiceList"""
for name in dir(self): for name in dir(self):
if name.startswith('check_'): if name.startswith('check_'):
method = getattr(self, name) method = getattr(self, name)
method(l) method(l)
class FileCheckBasicStructure(FileConstraintChecker): class FileCheckBasicStructure(FileConstraintChecker):
"""Validator for the basic structure of a decoded file."""
def check_seqence(self, l: FileChoiceList): def check_seqence(self, l: FileChoiceList):
"""Check the sequence/ordering."""
by_type = {} by_type = {}
for k, v in l: for k, v in l:
if k in by_type: if k in by_type:

View File

@@ -1,6 +1,5 @@
# Implementation of X.509 certificate handling in GSMA eSIM """Implementation of X.509 certificate handling in GSMA eSIM as per SGP22 v3.0"""
# as per SGP22 v3.0
#
# (C) 2024 by Harald Welte <laforge@osmocom.org> # (C) 2024 by Harald Welte <laforge@osmocom.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify