| # -*- coding: utf-8 -*- |
| |
| """ pySim: Card programmation logic |
| """ |
| |
| # |
| # Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com> |
| # Copyright (C) 2011-2023 Harald Welte <laforge@gnumonks.org> |
| # Copyright (C) 2017 Alexander.Chemeris <Alexander.Chemeris@gmail.com> |
| # |
| # 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 typing import Optional, Dict, Tuple |
| from pySim.ts_102_221 import EF_DIR |
| from pySim.ts_51_011 import DF_GSM |
| import abc |
| |
| from pySim.utils import * |
| from pySim.commands import Path, SimCardCommands |
| |
| class CardBase: |
| """General base class for some kind of telecommunications card.""" |
| def __init__(self, scc: SimCardCommands): |
| self._scc = scc |
| self._aids = [] |
| |
| def reset(self) -> Optional[Hexstr]: |
| rc = self._scc.reset_card() |
| if rc == 1: |
| return self._scc.get_atr() |
| else: |
| return None |
| |
| def set_apdu_parameter(self, cla: Hexstr, sel_ctrl: Hexstr) -> None: |
| """Set apdu parameters (class byte and selection control bytes)""" |
| self._scc.cla_byte = cla |
| self._scc.sel_ctrl = sel_ctrl |
| |
| def get_apdu_parameter(self) -> Tuple[Hexstr, Hexstr]: |
| """Get apdu parameters (class byte and selection control bytes)""" |
| return (self._scc.cla_byte, self._scc.sel_ctrl) |
| |
| def erase(self): |
| print("warning: erasing is not supported for specified card type!") |
| return |
| |
| def file_exists(self, fid: Path) -> bool: |
| res_arr = self._scc.try_select_path(fid) |
| for res in res_arr: |
| if res[1] != '9000': |
| return False |
| return True |
| |
| def read_aids(self) -> List[Hexstr]: |
| # a non-UICC doesn't have any applications. Convenience helper to avoid |
| # callers having to do hasattr('read_aids') ahead of every call. |
| return [] |
| |
| |
| class SimCardBase(CardBase): |
| """Here we only add methods for commands specified in TS 51.011, without |
| any higher-layer processing.""" |
| name = 'SIM' |
| |
| def __init__(self, scc: SimCardCommands): |
| super(SimCardBase, self).__init__(scc) |
| self._scc.cla_byte = "A0" |
| self._scc.sel_ctrl = "0000" |
| |
| def probe(self) -> bool: |
| df_gsm = DF_GSM() |
| return self.file_exists(df_gsm.fid) |
| |
| |
| class UiccCardBase(SimCardBase): |
| name = 'UICC' |
| |
| def __init__(self, scc: SimCardCommands): |
| super(UiccCardBase, self).__init__(scc) |
| self._scc.cla_byte = "00" |
| self._scc.sel_ctrl = "0004" # request an FCP |
| # See also: ETSI TS 102 221, Table 9.3 |
| self._adm_chv_num = 0x0A |
| |
| def probe(self) -> bool: |
| # EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM |
| ef_dir = EF_DIR() |
| return self.file_exists(ef_dir.fid) |
| |
| def read_aids(self) -> List[Hexstr]: |
| """Fetch all the AIDs present on UICC""" |
| self._aids = [] |
| try: |
| ef_dir = EF_DIR() |
| # Find out how many records the EF.DIR has |
| # and store all the AIDs in the UICC |
| rec_cnt = self._scc.record_count(ef_dir.fid) |
| for i in range(0, rec_cnt): |
| rec = self._scc.read_record(ef_dir.fid, i + 1) |
| if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \ |
| and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids: |
| self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2]) |
| except Exception as e: |
| print("Can't read AIDs from SIM -- %s" % (str(e),)) |
| self._aids = [] |
| return self._aids |
| |
| @staticmethod |
| def _get_aid(adf="usim") -> Optional[Hexstr]: |
| aid_map = {} |
| # First (known) halves of the U/ISIM AID |
| aid_map["usim"] = "a0000000871002" |
| aid_map["isim"] = "a0000000871004" |
| adf = adf.lower() |
| if adf in aid_map: |
| return aid_map[adf] |
| return None |
| |
| def _complete_aid(self, aid: Hexstr) -> Optional[Hexstr]: |
| """find the complete version of an ADF.U/ISIM AID""" |
| # Find full AID by partial AID: |
| if is_hex(aid): |
| for aid_known in self._aids: |
| if len(aid_known) >= len(aid) and aid == aid_known[0:len(aid)]: |
| return aid_known |
| return None |
| |
| def adf_present(self, adf: str = "usim") -> bool: |
| """Check if the AID of the specified ADF is present in EF.DIR (call read_aids before use)""" |
| aid = self._get_aid(adf) |
| if aid: |
| aid_full = self._complete_aid(aid) |
| if aid_full: |
| return True |
| return False |
| |
| def select_adf_by_aid(self, adf: str = "usim") -> Tuple[Optional[Hexstr], Optional[SwHexstr]]: |
| """Select ADF.U/ISIM in the Card using its full AID""" |
| if is_hex(adf): |
| aid = adf |
| else: |
| aid = self._get_aid(adf) |
| if aid: |
| aid_full = self._complete_aid(aid) |
| if aid_full: |
| return self._scc.select_adf(aid_full) |
| else: |
| # If we cannot get the full AID, try with short AID |
| return self._scc.select_adf(aid) |
| return (None, None) |
| |
| def card_detect(scc: SimCardCommands) -> Optional[CardBase]: |
| # UICC always has higher preference, as a UICC might also contain a SIM application |
| uicc = UiccCardBase(scc) |
| if uicc.probe(): |
| return uicc |
| |
| # this is for detecting a real, classic TS 11.11 SIM card without any UICC support |
| sim = SimCardBase(scc) |
| if sim.probe(): |
| return sim |
| |
| return None |