Johannes Waigel has uploaded this change for review.

View Change

osmo-smdpp: derive the Profile Metadata from the Profile

The metadata was invented: a hardcoded SPN, the MatchingID as
profileName, and always 'operational' as profileClass. Test Profiles
were therefore sent as operational ones, which SGP.22 section 2.4.5.3
forbids.

Add ProfileMetadata.from_pes() to read these fields from the Profile.
An LPA now shows Test Profiles only in Device Test Mode.

Change-Id: Ie24dd5ac0ef541c1899dd923a9db568209bbfa62
---
M osmo-smdpp.py
M pySim/esim/es8p.py
2 files changed, 35 insertions(+), 5 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/05/43205/1
diff --git a/osmo-smdpp.py b/osmo-smdpp.py
index 2a8e478..0346afc 100755
--- a/osmo-smdpp.py
+++ b/osmo-smdpp.py
@@ -629,7 +629,7 @@
# If ctxParams1 contains a ctxParamsForCommonAuthentication data object, the SM-DP+ Shall [...]
# TODO: We really do a very simplistic job here, this needs to be properly implemented later,
# considering all the various cases, profile state, etc.
- iccid_str = None
+ pes = None
if euiccSigned1['ctxParams1'][0] == 'ctxParamsForCommonAuthentication':
cpca = euiccSigned1['ctxParams1'][1]
matchingId = cpca.get('matchingId', None)
@@ -647,15 +647,14 @@
ss.matchingId = matchingId
with open(path, 'rb') as f:
pes = saip.ProfileElementSequence.from_der(f.read())
- iccid_str = b2h(pes.get_pe_for_type('header').decoded['iccid'])
else:
# there's currently no other option in the ctxParams1 choice, so this cannot happen
raise ApiError('1.3.1', '2.2', 'ctxParams1 missing mandatory ctxParamsForCommonAuthentication')

- # FIXME: we actually want to perform the profile binding herr, and read the profile metadata from the profile
+ # FIXME: we actually want to perform the profile binding here

# Put together profileMetadata + _bin
- ss.profileMetadata = ProfileMetadata(iccid_bin=h2b(swap_nibbles(iccid_str)), spn="OsmocomSPN", profile_name=matchingId)
+ ss.profileMetadata = ProfileMetadata.from_pes(pes, profile_name=matchingId)
# enable notifications for all operations
for event in ['enable', 'disable', 'delete']:
ss.profileMetadata.add_notification(event, self.server_hostname)
diff --git a/pySim/esim/es8p.py b/pySim/esim/es8p.py
index 6926cae..48ecfdd 100644
--- a/pySim/esim/es8p.py
+++ b/pySim/esim/es8p.py
@@ -17,13 +17,14 @@

from typing import Dict, List, Optional
from cryptography.hazmat.primitives.asymmetric import ec
-from osmocom.utils import b2h, h2b
+from osmocom.utils import b2h, h2b, swap_nibbles
from osmocom.tlv import bertlv_encode_tag, bertlv_encode_len, bertlv_parse_one_rawtag
from osmocom.tlv import bertlv_return_one_rawtlv

import pySim.esim.rsp as rsp
from pySim.esim.bsp import BspInstance
from pySim.esim import PMO
+from pySim.ts_51_011 import EF_SPN

import logging
logger = logging.getLogger(__name__)
@@ -73,6 +74,18 @@
return rsp.asn1.encode('ReplaceSessionKeysRequest', rsk)


+def spn_from_pes(pes) -> Optional[str]:
+ """Read the Service Provider Name from EF.SPN of the given Profile, or None if it has none."""
+ for pe_type in ['usim', 'gsm-access']:
+ pe = pes.get_pe_for_type(pe_type)
+ if not pe or 'ef-spn' not in getattr(pe, 'files', {}):
+ continue
+ content = pe.files['ef-spn'].file_content_from_tuples(pe.decoded['ef-spn'])
+ if content:
+ return EF_SPN().decode_hex(b2h(content))['spn']
+ return None
+
+
class ProfileMetadata:
"""Representation of Profile metadata. Right now only the mandatory bits are
supported, but in general this should follow the StoreMetadataRequest of SGP.22 5.5.3"""
@@ -85,6 +98,24 @@
self.icon_type = None
self.notifications = []

+ @classmethod
+ def from_pes(cls, pes, profile_name: Optional[str] = None) -> 'ProfileMetadata':
+ """Derive Profile metadata from the Profile itself, rather than from a separate source that
+ can disagree with it. profile_name is only used if the Profile header has no profileType."""
+ header = pes.get_pe_for_type('header').decoded
+
+ # SGP.22 Section 2.4.5.3: a Test Profile SHALL have its Profile Class set to 'test'. A
+ # Profile requiring the USIM test algorithm is one; that is condition 3 of that section.
+ if 'usim-test-algorithm' in header.get('eUICC-Mandatory-services', {}):
+ profile_class = 'test'
+ else:
+ profile_class = 'operational'
+
+ return cls(iccid_bin=h2b(swap_nibbles(b2h(header['iccid']))),
+ spn=spn_from_pes(pes) or '',
+ profile_name=header.get('profileType', profile_name) or '',
+ profile_class=profile_class)
+
def set_icon(self, is_png: bool, icon_data: bytes):
"""Set the icon that is part of the metadata."""
if len(icon_data) > 1024:

To view, visit change 43205. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: pysim
Gerrit-Branch: master
Gerrit-Change-Id: Ie24dd5ac0ef541c1899dd923a9db568209bbfa62
Gerrit-Change-Number: 43205
Gerrit-PatchSet: 1
Gerrit-Owner: Johannes Waigel <johannes@waigel.me>