| # -*- coding: utf-8 -*- |
| |
| """ pySim: tell old 2G SIMs apart from UICC |
| """ |
| |
| # |
| # (C) 2021 by Sysmocom s.f.m.c. GmbH |
| # All Rights Reserved |
| # |
| # This program is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation, either version 2 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| # |
| |
| from pySim.commands import SimCardCommands |
| from pySim.filesystem import CardApplication, interpret_sw |
| from pySim.utils import all_subclasses |
| import abc |
| import operator |
| from typing import List |
| |
| |
| def _mf_select_test(scc: SimCardCommands, |
| cla_byte: str, sel_ctrl: str, |
| fids: List[str]) -> bool: |
| cla_byte_bak = scc.cla_byte |
| sel_ctrl_bak = scc.sel_ctrl |
| scc.reset_card() |
| |
| scc.cla_byte = cla_byte |
| scc.sel_ctrl = sel_ctrl |
| rc = True |
| try: |
| for fid in fids: |
| scc.select_file(fid) |
| except: |
| rc = False |
| |
| scc.reset_card() |
| scc.cla_byte = cla_byte_bak |
| scc.sel_ctrl = sel_ctrl_bak |
| return rc |
| |
| |
| def match_uicc(scc: SimCardCommands) -> bool: |
| """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the |
| card is considered a UICC card. |
| """ |
| return _mf_select_test(scc, "00", "0004", ["3f00"]) |
| |
| |
| def match_sim(scc: SimCardCommands) -> bool: |
| """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card |
| is also a simcard. This will be the case for most UICC cards, but there may |
| also be plain UICC cards without 2G support as well. |
| """ |
| return _mf_select_test(scc, "a0", "0000", ["3f00"]) |
| |
| |
| def match_ruim(scc: SimCardCommands) -> bool: |
| """ Try to access MF/DF.CDMA via 2G APDUs (3GPP TS 11.11), if this works, |
| the card is considered an R-UIM card for CDMA. |
| """ |
| return _mf_select_test(scc, "a0", "0000", ["3f00", "7f25"]) |
| |
| |
| class CardProfile: |
| """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of |
| applications as well as profile-specific SW and shell commands. Every card has |
| one card profile, but there may be multiple applications within that profile.""" |
| |
| def __init__(self, name, **kw): |
| """ |
| Args: |
| desc (str) : Description |
| files_in_mf : List of CardEF instances present in MF |
| applications : List of CardApplications present on card |
| sw : List of status word definitions |
| 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) |
| self.files_in_mf = kw.get("files_in_mf", []) |
| self.sw = kw.get("sw", {}) |
| self.applications = kw.get("applications", []) |
| 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 |
| |
| def add_application(self, app: CardApplication): |
| """Add an application to a card profile. |
| |
| Args: |
| app : CardApplication instance to be added to profile |
| """ |
| self.applications.append(app) |
| |
| def interpret_sw(self, sw: str): |
| """Interpret a given status word within the profile. |
| |
| Args: |
| sw : Status word as string of 4 hex digits |
| |
| Returns: |
| Tuple of two strings |
| """ |
| return interpret_sw(self.sw, sw) |
| |
| @staticmethod |
| def decode_select_response(data_hex: str) -> object: |
| """Decode the response to a SELECT command. |
| |
| This is the fall-back method which doesn't perform any decoding. It mostly |
| exists so specific derived classes can overload it for actual decoding. |
| This method is implemented in the profile and is only used when application |
| specific decoding cannot be performed (no ADF is selected). |
| |
| Args: |
| data_hex: Hex string of the select response |
| """ |
| return data_hex |
| |
| @staticmethod |
| @abc.abstractmethod |
| def match_with_card(scc: SimCardCommands) -> bool: |
| """Check if the specific profile matches the card. This method is a |
| placeholder that is overloaded by specific dirived classes. The method |
| actively probes the card to make sure the profile class matches the |
| physical card. This usually also means that the card is reset during |
| the process, so this method must not be called at random times. It may |
| only be called on startup. |
| |
| Args: |
| scc: SimCardCommands class |
| Returns: |
| match = True, no match = False |
| """ |
| return False |
| |
| @staticmethod |
| def pick(scc: SimCardCommands): |
| profiles = list(all_subclasses(CardProfile)) |
| profiles.sort(key=operator.attrgetter('ORDER')) |
| |
| for p in profiles: |
| if p.match_with_card(scc): |
| 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 |