Sylvain Munaut | 76504e0 | 2010-12-07 00:24:32 +0100 | [diff] [blame] | 1 | # -*- coding: utf-8 -*- |
| 2 | |
| 3 | """ pySim: Card programmation logic |
| 4 | """ |
| 5 | |
| 6 | # |
| 7 | # Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com> |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 8 | # Copyright (C) 2011-2023 Harald Welte <laforge@gnumonks.org> |
Alexander Chemeris | eb6807d | 2017-07-18 17:04:38 +0300 | [diff] [blame] | 9 | # Copyright (C) 2017 Alexander.Chemeris <Alexander.Chemeris@gmail.com> |
Sylvain Munaut | 76504e0 | 2010-12-07 00:24:32 +0100 | [diff] [blame] | 10 | # |
| 11 | # This program is free software: you can redistribute it and/or modify |
| 12 | # it under the terms of the GNU General Public License as published by |
| 13 | # the Free Software Foundation, either version 2 of the License, or |
| 14 | # (at your option) any later version. |
| 15 | # |
| 16 | # This program is distributed in the hope that it will be useful, |
| 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 19 | # GNU General Public License for more details. |
| 20 | # |
| 21 | # You should have received a copy of the GNU General Public License |
| 22 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 23 | # |
| 24 | |
Vadim Yanitskiy | 03c67f7 | 2021-05-02 02:10:39 +0200 | [diff] [blame] | 25 | from typing import Optional, Dict, Tuple |
Philipp Maier | a42ee6f | 2023-08-16 10:44:57 +0200 | [diff] [blame] | 26 | from pySim.ts_102_221 import EF_DIR |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 27 | from pySim.ts_51_011 import DF_GSM |
Vadim Yanitskiy | 85302d6 | 2021-05-02 02:18:42 +0200 | [diff] [blame] | 28 | import abc |
Vadim Yanitskiy | 03c67f7 | 2021-05-02 02:10:39 +0200 | [diff] [blame] | 29 | |
Alexander Chemeris | eb6807d | 2017-07-18 17:04:38 +0300 | [diff] [blame] | 30 | from pySim.utils import * |
Harald Welte | 7997252 | 2023-10-21 20:19:32 +0200 | [diff] [blame] | 31 | from pySim.commands import Path, SimCardCommands |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 32 | |
| 33 | class CardBase: |
| 34 | """General base class for some kind of telecommunications card.""" |
Harald Welte | 7997252 | 2023-10-21 20:19:32 +0200 | [diff] [blame] | 35 | def __init__(self, scc: SimCardCommands): |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 36 | self._scc = scc |
| 37 | self._aids = [] |
| 38 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 39 | def reset(self) -> Optional[Hexstr]: |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 40 | rc = self._scc.reset_card() |
| 41 | if rc == 1: |
| 42 | return self._scc.get_atr() |
| 43 | else: |
| 44 | return None |
| 45 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 46 | def set_apdu_parameter(self, cla: Hexstr, sel_ctrl: Hexstr) -> None: |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 47 | """Set apdu parameters (class byte and selection control bytes)""" |
| 48 | self._scc.cla_byte = cla |
| 49 | self._scc.sel_ctrl = sel_ctrl |
| 50 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 51 | def get_apdu_parameter(self) -> Tuple[Hexstr, Hexstr]: |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 52 | """Get apdu parameters (class byte and selection control bytes)""" |
| 53 | return (self._scc.cla_byte, self._scc.sel_ctrl) |
| 54 | |
| 55 | def erase(self): |
| 56 | print("warning: erasing is not supported for specified card type!") |
| 57 | return |
| 58 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 59 | def file_exists(self, fid: Path) -> bool: |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 60 | res_arr = self._scc.try_select_path(fid) |
| 61 | for res in res_arr: |
| 62 | if res[1] != '9000': |
| 63 | return False |
| 64 | return True |
| 65 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 66 | def read_aids(self) -> List[Hexstr]: |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 67 | # a non-UICC doesn't have any applications. Convenience helper to avoid |
| 68 | # callers having to do hasattr('read_aids') ahead of every call. |
| 69 | return [] |
| 70 | |
| 71 | |
| 72 | class SimCardBase(CardBase): |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 73 | """Here we only add methods for commands specified in TS 51.011, without |
| 74 | any higher-layer processing.""" |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 75 | name = 'SIM' |
| 76 | |
Harald Welte | 7997252 | 2023-10-21 20:19:32 +0200 | [diff] [blame] | 77 | def __init__(self, scc: SimCardCommands): |
Harald Welte | 172c28e | 2023-07-10 22:20:40 +0200 | [diff] [blame] | 78 | super(SimCardBase, self).__init__(scc) |
| 79 | self._scc.cla_byte = "A0" |
Harald Welte | 659d7c1 | 2023-07-10 22:25:58 +0200 | [diff] [blame] | 80 | self._scc.sel_ctrl = "0000" |
Harald Welte | 172c28e | 2023-07-10 22:20:40 +0200 | [diff] [blame] | 81 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 82 | def probe(self) -> bool: |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 83 | df_gsm = DF_GSM() |
| 84 | return self.file_exists(df_gsm.fid) |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 85 | |
| 86 | |
| 87 | class UiccCardBase(SimCardBase): |
| 88 | name = 'UICC' |
| 89 | |
Harald Welte | 7997252 | 2023-10-21 20:19:32 +0200 | [diff] [blame] | 90 | def __init__(self, scc: SimCardCommands): |
Harald Welte | 775ab01 | 2023-07-10 22:23:07 +0200 | [diff] [blame] | 91 | super(UiccCardBase, self).__init__(scc) |
Harald Welte | 172c28e | 2023-07-10 22:20:40 +0200 | [diff] [blame] | 92 | self._scc.cla_byte = "00" |
Harald Welte | 659d7c1 | 2023-07-10 22:25:58 +0200 | [diff] [blame] | 93 | self._scc.sel_ctrl = "0004" # request an FCP |
Harald Welte | 172c28e | 2023-07-10 22:20:40 +0200 | [diff] [blame] | 94 | # See also: ETSI TS 102 221, Table 9.3 |
Philipp Maier | 3175d61 | 2023-07-20 17:28:10 +0200 | [diff] [blame] | 95 | self._adm_chv_num = 0x0A |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 96 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 97 | def probe(self) -> bool: |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 98 | # EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM |
| 99 | ef_dir = EF_DIR() |
| 100 | return self.file_exists(ef_dir.fid) |
Harald Welte | 263fb08 | 2023-07-09 17:15:36 +0200 | [diff] [blame] | 101 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 102 | def read_aids(self) -> List[Hexstr]: |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 103 | """Fetch all the AIDs present on UICC""" |
| 104 | self._aids = [] |
| 105 | try: |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 106 | ef_dir = EF_DIR() |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 107 | # Find out how many records the EF.DIR has |
| 108 | # and store all the AIDs in the UICC |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 109 | rec_cnt = self._scc.record_count(ef_dir.fid) |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 110 | for i in range(0, rec_cnt): |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 111 | rec = self._scc.read_record(ef_dir.fid, i + 1) |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 112 | if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \ |
| 113 | and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids: |
| 114 | self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2]) |
| 115 | except Exception as e: |
| 116 | print("Can't read AIDs from SIM -- %s" % (str(e),)) |
| 117 | self._aids = [] |
| 118 | return self._aids |
Supreeth Herle | e4e9831 | 2020-03-18 11:33:14 +0100 | [diff] [blame] | 119 | |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 120 | @staticmethod |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 121 | def _get_aid(adf="usim") -> Optional[Hexstr]: |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 122 | aid_map = {} |
| 123 | # First (known) halves of the U/ISIM AID |
| 124 | aid_map["usim"] = "a0000000871002" |
| 125 | aid_map["isim"] = "a0000000871004" |
| 126 | adf = adf.lower() |
| 127 | if adf in aid_map: |
| 128 | return aid_map[adf] |
| 129 | return None |
Philipp Maier | 46c6154 | 2021-11-16 16:36:50 +0100 | [diff] [blame] | 130 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 131 | def _complete_aid(self, aid: Hexstr) -> Optional[Hexstr]: |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 132 | """find the complete version of an ADF.U/ISIM AID""" |
| 133 | # Find full AID by partial AID: |
| 134 | if is_hex(aid): |
| 135 | for aid_known in self._aids: |
| 136 | if len(aid_known) >= len(aid) and aid == aid_known[0:len(aid)]: |
| 137 | return aid_known |
| 138 | return None |
Philipp Maier | 46c6154 | 2021-11-16 16:36:50 +0100 | [diff] [blame] | 139 | |
Harald Welte | a396129 | 2023-07-09 21:44:52 +0200 | [diff] [blame] | 140 | def adf_present(self, adf: str = "usim") -> bool: |
Philipp Maier | 8490240 | 2023-02-10 18:23:36 +0100 | [diff] [blame] | 141 | """Check if the AID of the specified ADF is present in EF.DIR (call read_aids before use)""" |
| 142 | aid = self._get_aid(adf) |
| 143 | if aid: |
| 144 | aid_full = self._complete_aid(aid) |
| 145 | if aid_full: |
| 146 | return True |
| 147 | return False |
| 148 | |
Harald Welte | 6dd6f3e | 2023-10-22 10:28:18 +0200 | [diff] [blame] | 149 | def select_adf_by_aid(self, adf: str = "usim", scc: Optional[SimCardCommands] = None) -> Tuple[Optional[Hexstr], Optional[SwHexstr]]: |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 150 | """Select ADF.U/ISIM in the Card using its full AID""" |
Harald Welte | 6dd6f3e | 2023-10-22 10:28:18 +0200 | [diff] [blame] | 151 | # caller may pass a custom scc; we fall back to default |
| 152 | scc = scc or self._scc |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 153 | if is_hex(adf): |
| 154 | aid = adf |
| 155 | else: |
| 156 | aid = self._get_aid(adf) |
| 157 | if aid: |
| 158 | aid_full = self._complete_aid(aid) |
| 159 | if aid_full: |
Harald Welte | 6dd6f3e | 2023-10-22 10:28:18 +0200 | [diff] [blame] | 160 | return scc.select_adf(aid_full) |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 161 | else: |
| 162 | # If we cannot get the full AID, try with short AID |
Harald Welte | 6dd6f3e | 2023-10-22 10:28:18 +0200 | [diff] [blame] | 163 | return scc.select_adf(aid) |
Harald Welte | c91085e | 2022-02-10 18:05:45 +0100 | [diff] [blame] | 164 | return (None, None) |
Supreeth Herle | f9f3e5e | 2020-03-22 08:04:59 +0100 | [diff] [blame] | 165 | |
Harald Welte | 7997252 | 2023-10-21 20:19:32 +0200 | [diff] [blame] | 166 | def card_detect(scc: SimCardCommands) -> Optional[CardBase]: |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 167 | # UICC always has higher preference, as a UICC might also contain a SIM application |
| 168 | uicc = UiccCardBase(scc) |
| 169 | if uicc.probe(): |
| 170 | return uicc |
Philipp Maier | bda5283 | 2022-06-14 16:18:12 +0200 | [diff] [blame] | 171 | |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 172 | # this is for detecting a real, classic TS 11.11 SIM card without any UICC support |
| 173 | sim = SimCardBase(scc) |
| 174 | if sim.probe(): |
| 175 | return sim |
Harald Welte | ca67394 | 2020-06-03 15:19:40 +0200 | [diff] [blame] | 176 | |
Harald Welte | f8d2e2b | 2023-07-09 17:58:38 +0200 | [diff] [blame] | 177 | return None |