From 323a35043f360080f7e100dbb9f16929f685371d Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Tue, 11 Jul 2023 17:26:39 +0200 Subject: [PATCH] Introduce concept of CardProfileAddon We have a strict "one CardProfile per card" rule. For a modern UICC without legacy SIM support, that works great, as all applications have AID and ADF and can hence be enumerated/detected that way. However, in reality there are mostly UICC that have legacy SIM, GSM-R or even CDMA support, all of which are not proper UICC applications for historical reasons. So instead of having hard-coded hacks in various places, let's introduce the new concept of a CardProfileAddon. Every profile can have any number of those. When building up the RuntimeState, we iterate over the CardProfile addons, and probe which of those are actually on the card. For those discovered, we add their files to the filesystem hierarchy. Change-Id: I5866590b6d48f85eb889c9b1b8ab27936d2378b9 --- pySim-shell.py | 5 ----- pySim-trace.py | 7 ++++--- pySim/cdma_ruim.py | 13 ++++++++++++- pySim/filesystem.py | 10 ++++++++++ pySim/gsm_r.py | 14 +++++++++++++- pySim/profile.py | 34 ++++++++++++++++++++++++++++++++++ pySim/ts_102_221.py | 14 +++++++++++--- pySim/ts_51_011.py | 23 +++++++++++++++++++++-- 8 files changed, 105 insertions(+), 15 deletions(-) diff --git a/pySim-shell.py b/pySim-shell.py index 4d4f2e23..b48abc74 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -137,11 +137,6 @@ def init_card(sl): # Create runtime state with card profile rs = RuntimeState(card, profile) - # FIXME: This is an GSM-R related file, it needs to be added throughout, - # the profile. At the moment we add it for all cards, this won't hurt, - # but regular SIM and UICC will not have it and fail to select it. - rs.mf.add_file(DF_EIRENE()) - CardModel.apply_matching_models(scc, rs) # inform the transport that we can do context-specific SW interpretation diff --git a/pySim-trace.py b/pySim-trace.py index 4c8696d4..ba1568cf 100755 --- a/pySim-trace.py +++ b/pySim-trace.py @@ -11,7 +11,7 @@ from pySim.filesystem import RuntimeState from pySim.cards import UiccCardBase from pySim.commands import SimCardCommands from pySim.profile import CardProfile -from pySim.ts_102_221 import CardProfileUICCSIM +from pySim.ts_102_221 import CardProfileUICC from pySim.ts_31_102 import CardApplicationUSIM from pySim.ts_31_103 import CardApplicationISIM from pySim.transport import LinkBase @@ -70,8 +70,9 @@ class DummySimLink(LinkBase): class Tracer: def __init__(self, **kwargs): - # we assume a generic SIM + UICC + USIM + ISIM card - profile = CardProfileUICCSIM() + # we assume a generic UICC profile; as all APDUs return 9000 in DummySimLink above, + # all CardProfileAddon (including SIM) will probe successful. + profile = CardProfileUICC() profile.add_application(CardApplicationUSIM()) profile.add_application(CardApplicationISIM()) scc = SimCardCommands(transport=DummySimLink()) diff --git a/pySim/cdma_ruim.py b/pySim/cdma_ruim.py index 8b664902..b254403f 100644 --- a/pySim/cdma_ruim.py +++ b/pySim/cdma_ruim.py @@ -22,7 +22,7 @@ import enum from pySim.utils import * from pySim.filesystem import * from pySim.profile import match_ruim -from pySim.profile import CardProfile +from pySim.profile import CardProfile, CardProfileAddon from pySim.ts_51_011 import CardProfileSIM from pySim.ts_51_011 import DF_TELECOM, DF_GSM from pySim.ts_51_011 import EF_ServiceTable @@ -191,3 +191,14 @@ class CardProfileRUIM(CardProfile): @staticmethod def match_with_card(scc: SimCardCommands) -> bool: return match_ruim(scc) + +class AddonRUIM(CardProfileAddon): + """An Addon that can be found on on a combined SIM + RUIM or UICC + RUIM to support CDMA.""" + def __init__(self): + files = [ + DF_CDMA() + ] + super().__init__('RUIM', desc='CDMA RUIM', files_in_mf=files) + + def probe(self, card: 'CardBase') -> bool: + return card.file_exists(self.files_in_mf[0].fid) diff --git a/pySim/filesystem.py b/pySim/filesystem.py index 22ff60dc..cb3b403e 100644 --- a/pySim/filesystem.py +++ b/pySim/filesystem.py @@ -1307,6 +1307,16 @@ class RuntimeState: self.card.set_apdu_parameter( cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl) + for addon_cls in self.profile.addons: + addon = addon_cls() + if addon.probe(self.card): + print("Detected %s Add-on \"%s\"" % (self.profile, addon)) + for f in addon.files_in_mf: + self.mf.add_file(f) + + # go back to MF before the next steps (addon probing might have changed DF) + self.card._scc.select_file('3F00') + # add application ADFs + MF-files from profile apps = self._match_applications() for a in apps: diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py index 389a8cb8..cd111d68 100644 --- a/pySim/gsm_r.py +++ b/pySim/gsm_r.py @@ -35,8 +35,8 @@ from construct import Optional as COptional from pySim.construct import * import enum +from pySim.profile import CardProfileAddon from pySim.filesystem import * -import pySim.ts_102_221 import pySim.ts_51_011 ###################################################################### @@ -362,3 +362,15 @@ class DF_EIRENE(CardDF): desc='Free Number Call Type 0 and 8'), ] self.add_files(files) + + +class AddonGSMR(CardProfileAddon): + """An Addon that can be found on either classic GSM SIM or on UICC to support GSM-R.""" + def __init__(self): + files = [ + DF_EIRENE() + ] + super().__init__('GSM-R', desc='Railway GSM', files_in_mf=files) + + def probe(self, card: 'CardBase') -> bool: + return card.file_exists(self.files_in_mf[0].fid) diff --git a/pySim/profile.py b/pySim/profile.py index e464e1f9..0d09e81b 100644 --- a/pySim/profile.py +++ b/pySim/profile.py @@ -88,6 +88,7 @@ class CardProfile: shell_cmdsets : List of cmd2 shell command sets of profile-specific commands cla : class byte that should be used with cards of this profile sel_ctrl : selection control bytes class byte that should be used with cards of this profile + addons: List of optional CardAddons that a card of this profile might have """ self.name = name self.desc = kw.get("desc", None) @@ -97,6 +98,8 @@ class CardProfile: self.shell_cmdsets = kw.get("shell_cmdsets", []) self.cla = kw.get("cla", "00") self.sel_ctrl = kw.get("sel_ctrl", "0004") + # list of optional addons that a card of this profile might have + self.addons = kw.get("addons", []) def __str__(self): return self.name @@ -161,3 +164,34 @@ class CardProfile: return p() return None + + def add_addon(self, addon: 'CardProfileAddon'): + assert(addon not in self.addons) + # we don't install any additional files, as that is happening in the RuntimeState. + self.addons.append(addon) + +class CardProfileAddon(abc.ABC): + """A Card Profile Add-on is something that is not a card application or a full stand-alone + card profile, but an add-on to an existing profile. Think of GSM-R specific files existing + on what is otherwise a SIM or USIM+SIM card.""" + + def __init__(self, name: str, **kw): + """ + Args: + desc (str) : Description + files_in_mf : List of CardEF instances present in MF + shell_cmdsets : List of cmd2 shell command sets of profile-specific commands + """ + self.name = name + self.desc = kw.get("desc", None) + self.files_in_mf = kw.get("files_in_mf", []) + self.shell_cmdsets = kw.get("shell_cmdsets", []) + pass + + def __str__(self): + return self.name + + @abc.abstractmethod + def probe(self, card: 'CardBase') -> bool: + """Probe a given card to determine whether or not this add-on is present/supported.""" + pass diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py index b6c003b8..df8b8421 100644 --- a/pySim/ts_102_221.py +++ b/pySim/ts_102_221.py @@ -31,7 +31,9 @@ import pySim.iso7816_4 as iso7816_4 # A UICC will usually also support 2G functionality. If this is the case, we # need to add DF_GSM and DF_TELECOM along with the UICC related files -from pySim.ts_51_011 import DF_GSM, DF_TELECOM +from pySim.ts_51_011 import DF_GSM, DF_TELECOM, AddonSIM +from pySim.gsm_r import AddonGSMR +from pySim.cdma_ruim import AddonRUIM ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [ # TS 102 221 Section 10.1.2 Table 10.5 "Coding of Instruction Byte" @@ -768,6 +770,11 @@ class CardProfileUICC(CardProfile): # FIXME: DF.CD EF_UMPC(), ] + addons = [ + AddonSIM, + AddonGSMR, + AddonRUIM, + ] sw = { 'Normal': { '9000': 'Normal ending of the command', @@ -839,7 +846,7 @@ class CardProfileUICC(CardProfile): super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw, - shell_cmdsets = [self.AddlShellCommands()]) + shell_cmdsets = [self.AddlShellCommands()], addons = addons) @staticmethod def decode_select_response(resp_hex: str) -> object: @@ -895,4 +902,5 @@ class CardProfileUICCSIM(CardProfileUICC): @staticmethod def match_with_card(scc: SimCardCommands) -> bool: - return match_uicc(scc) and match_sim(scc) + # don't ever select this profile, we only use this from pySim-trace + return False diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py index 1f98e724..c81bfdfb 100644 --- a/pySim/ts_51_011.py +++ b/pySim/ts_51_011.py @@ -30,9 +30,10 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications # from pySim.profile import match_sim -from pySim.profile import CardProfile +from pySim.profile import CardProfile, CardProfileAddon from pySim.filesystem import * from pySim.ts_31_102_telecom import DF_PHONEBOOK, DF_MULTIMEDIA, DF_MCS, DF_V2X +from pySim.gsm_r import AddonGSMR import enum from pySim.construct import * from construct import Optional as COptional @@ -1047,8 +1048,12 @@ class CardProfileSIM(CardProfile): }, } + addons = [ + AddonGSMR, + ] + super().__init__('SIM', desc='GSM SIM Card', cla="a0", - sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw) + sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw, addons = addons) @staticmethod def decode_select_response(resp_hex: str) -> object: @@ -1104,3 +1109,17 @@ class CardProfileSIM(CardProfile): @staticmethod def match_with_card(scc: SimCardCommands) -> bool: return match_sim(scc) + + +class AddonSIM(CardProfileAddon): + """An add-on that can be found on a UICC in order to support classic GSM SIM.""" + def __init__(self): + files = [ + DF_GSM(), + DF_TELECOM(), + ] + super().__init__('SIM', desc='GSM SIM', files_in_mf=files) + + def probe(self, card:'CardBase') -> bool: + # we assume the add-on to be present in case DF.GSM is found on the card + return card.file_exists(self.files_in_mf[0].fid)