blob: d785251382ddac03c8b41944397cfbff6e1b113b [file] [log] [blame]
Sylvain Munaut76504e02010-12-07 00:24:32 +01001# -*- coding: utf-8 -*-
2
3""" pySim: SIM Card commands according to ISO 7816-4 and TS 11.11
4"""
5
6#
7# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
Harald Weltefdb187d2023-07-09 17:03:17 +02008# Copyright (C) 2010-2023 Harald Welte <laforge@gnumonks.org>
Sylvain Munaut76504e02010-12-07 00:24:32 +01009#
10# This program is free software: you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation, either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program. If not, see <http://www.gnu.org/licenses/>.
22#
23
Harald Weltefdb187d2023-07-09 17:03:17 +020024from typing import List, Optional, Tuple
25import typing # construct also has a Union, so we do typing.Union below
26
Harald Welte15fae982021-04-10 10:22:27 +020027from construct import *
28from pySim.construct import LV
Harald Welte3dfab9d2023-10-21 23:38:32 +020029from pySim.utils import rpad, lpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, i2h, str_sanitize, expand_hex
Harald Weltefdb187d2023-07-09 17:03:17 +020030from pySim.utils import Hexstr, SwHexstr, ResTuple
Philipp Maier46f09af2021-03-25 20:24:27 +010031from pySim.exceptions import SwMatchError
Harald Weltefdb187d2023-07-09 17:03:17 +020032from pySim.transport import LinkBase
Sylvain Munaut76504e02010-12-07 00:24:32 +010033
Harald Weltefdb187d2023-07-09 17:03:17 +020034# A path can be either just a FID or a list of FID
35Path = typing.Union[Hexstr, List[Hexstr]]
Harald Weltec91085e2022-02-10 18:05:45 +010036
Harald Welte3dfab9d2023-10-21 23:38:32 +020037def lchan_nr_to_cla(cla: int, lchan_nr: int) -> int:
38 """Embed a logical channel number into the CLA byte."""
39 # TS 102 221 10.1.1 Coding of Class Byte
40 if lchan_nr < 4:
41 # standard logical channel number
42 if cla >> 4 in [0x0, 0xA, 0x8]:
43 return (cla & 0xFC) | (lchan_nr & 3)
44 else:
45 raise ValueError('Undefined how to use CLA %2X with logical channel %u' % (cla, lchan_nr))
46 elif lchan_nr < 16:
47 # extended logical channel number
48 if cla >> 6 in [1, 3]:
49 return (cla & 0xF0) | ((lchan_nr - 4) & 0x0F)
50 else:
51 raise ValueError('Undefined how to use CLA %2X with logical channel %u' % (cla, lchan_nr))
52 else:
53 raise ValueError('logical channel outside of range 0 .. 15')
54
55def cla_with_lchan(cla_byte: Hexstr, lchan_nr: int) -> Hexstr:
56 """Embed a logical channel number into the hex-string encoded CLA value."""
57 cla_int = h2i(cla_byte)[0]
58 return i2h([lchan_nr_to_cla(cla_int, lchan_nr)])
59
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +070060class SimCardCommands:
Harald Welte3dfab9d2023-10-21 23:38:32 +020061 """Class providing methods for various card-specific commands such as SELECT, READ BINARY, etc.
62 Historically one instance exists below CardBase, but with the introduction of multiple logical
63 channels there can be multiple instances. The lchan number will then be patched into the CLA
64 byte by the respective instance. """
65 def __init__(self, transport: LinkBase, lchan_nr: int = 0):
Harald Weltec91085e2022-02-10 18:05:45 +010066 self._tp = transport
Harald Welte3dfab9d2023-10-21 23:38:32 +020067 self._cla_byte = None
Harald Weltec91085e2022-02-10 18:05:45 +010068 self.sel_ctrl = "0000"
Harald Welte3dfab9d2023-10-21 23:38:32 +020069 self.lchan_nr = lchan_nr
70 # invokes the setter below
71 self.cla_byte = "a0"
72
73 def fork_lchan(self, lchan_nr: int) -> 'SimCardCommands':
74 """Fork a per-lchan specific SimCardCommands instance off the current instance."""
75 ret = SimCardCommands(transport = self._tp, lchan_nr = lchan_nr)
76 ret.cla_byte = self._cla_byte
Harald Welte46255122023-10-21 23:40:42 +020077 ret.sel_ctrl = self.sel_ctrl
Harald Welte3dfab9d2023-10-21 23:38:32 +020078 return ret
79
80 @property
81 def cla_byte(self) -> Hexstr:
82 """Return the (cached) patched default CLA byte for this card."""
83 return self._cla4lchan
84
85 @cla_byte.setter
86 def cla_byte(self, new_val: Hexstr):
87 """Set the (raw, without lchan) default CLA value for this card."""
88 self._cla_byte = new_val
89 # compute cached result
90 self._cla4lchan = cla_with_lchan(self._cla_byte, self.lchan_nr)
91
92 def cla4lchan(self, cla: Hexstr) -> Hexstr:
93 """Compute the lchan-patched value of the given CLA value. If no CLA
94 value is provided as argument, the lchan-patched version of the SimCardCommands._cla_byte
95 value is used. Most commands will use the latter, while some wish to override it and
96 can pass it as argument here."""
97 if not cla:
98 # return cached result to avoid re-computing this over and over again
99 return self._cla4lchan
100 else:
101 return cla_with_lchan(cla, self.lchan_nr)
Jan Balke14b350f2015-01-26 11:15:25 +0100102
Harald Weltec91085e2022-02-10 18:05:45 +0100103 # Extract a single FCP item from TLV
Harald Weltefdb187d2023-07-09 17:03:17 +0200104 def __parse_fcp(self, fcp: Hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100105 # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
106 # DF or ADF
107 from pytlv.TLV import TLV
108 tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b',
109 '8c', '80', 'ab', 'c6', '81', '88'])
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200110
Harald Weltec91085e2022-02-10 18:05:45 +0100111 # pytlv is case sensitive!
112 fcp = fcp.lower()
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200113
Harald Weltec91085e2022-02-10 18:05:45 +0100114 if fcp[0:2] != '62':
115 raise ValueError(
116 'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2])
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200117
Harald Weltec91085e2022-02-10 18:05:45 +0100118 # Unfortunately the spec is not very clear if the FCP length is
119 # coded as one or two byte vale, so we have to try it out by
120 # checking if the length of the remaining TLV string matches
121 # what we get in the length field.
122 # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
123 exp_tlv_len = int(fcp[2:4], 16)
124 if len(fcp[4:]) // 2 == exp_tlv_len:
125 skip = 4
126 else:
127 exp_tlv_len = int(fcp[2:6], 16)
128 if len(fcp[4:]) // 2 == exp_tlv_len:
129 skip = 6
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200130
Harald Weltec91085e2022-02-10 18:05:45 +0100131 # Skip FCP tag and length
132 tlv = fcp[skip:]
133 return tlvparser.parse(tlv)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200134
Harald Weltec91085e2022-02-10 18:05:45 +0100135 # Tell the length of a record by the card response
136 # USIMs respond with an FCP template, which is different
137 # from what SIMs responds. See also:
138 # USIM: ETSI TS 102 221, chapter 11.1.1.3 Response Data
139 # SIM: GSM 11.11, chapter 9.2.1 SELECT
140 def __record_len(self, r) -> int:
141 if self.sel_ctrl == "0004":
142 tlv_parsed = self.__parse_fcp(r[-1])
143 file_descriptor = tlv_parsed['82']
144 # See also ETSI TS 102 221, chapter 11.1.1.4.3 File Descriptor
145 return int(file_descriptor[4:8], 16)
146 else:
147 return int(r[-1][28:30], 16)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200148
Harald Weltec91085e2022-02-10 18:05:45 +0100149 # Tell the length of a binary file. See also comment
150 # above.
151 def __len(self, r) -> int:
152 if self.sel_ctrl == "0004":
153 tlv_parsed = self.__parse_fcp(r[-1])
154 return int(tlv_parsed['80'], 16)
155 else:
156 return int(r[-1][4:8], 16)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200157
Harald Weltefdb187d2023-07-09 17:03:17 +0200158 def get_atr(self) -> Hexstr:
Harald Weltec91085e2022-02-10 18:05:45 +0100159 """Return the ATR of the currently inserted card."""
160 return self._tp.get_atr()
Alexander Chemerisd2d660a2017-07-18 16:52:25 +0300161
Harald Weltefdb187d2023-07-09 17:03:17 +0200162 def try_select_path(self, dir_list: List[Hexstr]) -> List[ResTuple]:
Harald Weltec91085e2022-02-10 18:05:45 +0100163 """ Try to select a specified path
Philipp Maier712251a2021-11-04 17:09:37 +0100164
Harald Weltec91085e2022-02-10 18:05:45 +0100165 Args:
166 dir_list : list of hex-string FIDs
167 """
Philipp Maier712251a2021-11-04 17:09:37 +0100168
Harald Weltec91085e2022-02-10 18:05:45 +0100169 rv = []
170 if type(dir_list) is not list:
171 dir_list = [dir_list]
172 for i in dir_list:
173 data, sw = self._tp.send_apdu(
174 self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
175 rv.append((data, sw))
176 if sw != '9000':
177 return rv
178 return rv
Harald Welteca673942020-06-03 15:19:40 +0200179
Harald Weltefdb187d2023-07-09 17:03:17 +0200180 def select_path(self, dir_list: Path) -> List[Hexstr]:
Harald Weltec91085e2022-02-10 18:05:45 +0100181 """Execute SELECT for an entire list/path of FIDs.
Harald Welteee3501f2021-04-02 13:00:18 +0200182
Harald Weltec91085e2022-02-10 18:05:45 +0100183 Args:
184 dir_list: list of FIDs representing the path to select
Harald Welteee3501f2021-04-02 13:00:18 +0200185
Harald Weltec91085e2022-02-10 18:05:45 +0100186 Returns:
187 list of return values (FCP in hex encoding) for each element of the path
188 """
189 rv = []
190 if type(dir_list) is not list:
191 dir_list = [dir_list]
192 for i in dir_list:
193 data, sw = self.select_file(i)
194 rv.append(data)
195 return rv
Sylvain Munaut76504e02010-12-07 00:24:32 +0100196
Harald Weltefdb187d2023-07-09 17:03:17 +0200197 def select_file(self, fid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100198 """Execute SELECT a given file by FID.
Philipp Maier712251a2021-11-04 17:09:37 +0100199
Harald Weltec91085e2022-02-10 18:05:45 +0100200 Args:
201 fid : file identifier as hex string
202 """
Philipp Maier712251a2021-11-04 17:09:37 +0100203
Harald Weltec91085e2022-02-10 18:05:45 +0100204 return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid)
Harald Welte85484a92021-01-21 16:08:56 +0100205
Harald Weltefdb187d2023-07-09 17:03:17 +0200206 def select_parent_df(self) -> ResTuple:
Harald Welte3729c472022-02-12 14:36:37 +0100207 """Execute SELECT to switch to the parent DF """
208 return self._tp.send_apdu_checksw(self.cla_byte + "a4030400")
209
Harald Weltefdb187d2023-07-09 17:03:17 +0200210 def select_adf(self, aid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100211 """Execute SELECT a given Applicaiton ADF.
Philipp Maier712251a2021-11-04 17:09:37 +0100212
Harald Weltec91085e2022-02-10 18:05:45 +0100213 Args:
214 aid : application identifier as hex string
215 """
Philipp Maier712251a2021-11-04 17:09:37 +0100216
Harald Weltec91085e2022-02-10 18:05:45 +0100217 aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:]
218 return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid)
Philipp Maier0ad5bcf2019-12-31 17:55:47 +0100219
Harald Weltefdb187d2023-07-09 17:03:17 +0200220 def read_binary(self, ef: Path, length: int = None, offset: int = 0) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100221 """Execute READD BINARY.
Harald Welteee3501f2021-04-02 13:00:18 +0200222
Harald Weltec91085e2022-02-10 18:05:45 +0100223 Args:
224 ef : string or list of strings indicating name or path of transparent EF
225 length : number of bytes to read
226 offset : byte offset in file from which to start reading
227 """
228 r = self.select_path(ef)
229 if len(r[-1]) == 0:
230 return (None, None)
231 if length is None:
232 length = self.__len(r) - offset
233 if length < 0:
234 return (None, None)
Philipp Maiere087f902021-11-03 11:46:05 +0100235
Harald Weltec91085e2022-02-10 18:05:45 +0100236 total_data = ''
237 chunk_offset = 0
238 while chunk_offset < length:
239 chunk_len = min(255, length-chunk_offset)
240 pdu = self.cla_byte + \
241 'b0%04x%02x' % (offset + chunk_offset, chunk_len)
242 try:
243 data, sw = self._tp.send_apdu_checksw(pdu)
244 except Exception as e:
245 raise ValueError('%s, failed to read (offset %d)' %
246 (str_sanitize(str(e)), offset))
247 total_data += data
248 chunk_offset += chunk_len
249 return total_data, sw
Sylvain Munaut76504e02010-12-07 00:24:32 +0100250
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200251 def __verify_binary(self, ef, data: str, offset: int = 0):
252 """Verify contents of transparent EF.
253
254 Args:
255 ef : string or list of strings indicating name or path of transparent EF
256 data : hex string of expected data
257 offset : byte offset in file from which to start verifying
258 """
259 res = self.read_binary(ef, len(data) // 2, offset)
260 if res[0].lower() != data.lower():
261 raise ValueError('Binary verification failed (expected %s, got %s)' % (
262 data.lower(), res[0].lower()))
263
Harald Weltefdb187d2023-07-09 17:03:17 +0200264 def update_binary(self, ef: Path, data: Hexstr, offset: int = 0, verify: bool = False,
265 conserve: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100266 """Execute UPDATE BINARY.
Harald Welteee3501f2021-04-02 13:00:18 +0200267
Harald Weltec91085e2022-02-10 18:05:45 +0100268 Args:
269 ef : string or list of strings indicating name or path of transparent EF
270 data : hex string of data to be written
271 offset : byte offset in file from which to start writing
272 verify : Whether or not to verify data after write
273 """
Philipp Maier40ea4a42022-06-02 14:45:41 +0200274
275 file_len = self.binary_size(ef)
276 data = expand_hex(data, file_len)
277
Harald Weltec91085e2022-02-10 18:05:45 +0100278 data_length = len(data) // 2
Philipp Maier38c74f62021-03-17 17:19:52 +0100279
Harald Weltec91085e2022-02-10 18:05:45 +0100280 # Save write cycles by reading+comparing before write
281 if conserve:
Harald Welte1c0a2492023-12-28 15:42:51 +0100282 try:
283 data_current, sw = self.read_binary(ef, data_length, offset)
284 if data_current == data:
285 return None, sw
286 except Exception:
287 # cannot read data. This is not a fatal error, as reading is just done to
288 # conserve the amount of smart card writes. The access conditions of the file
289 # may well permit us to UPDATE but not permit us to READ. So let's ignore
290 # any such exception during READ.
291 pass
Philipp Maier38c74f62021-03-17 17:19:52 +0100292
Harald Weltec91085e2022-02-10 18:05:45 +0100293 self.select_path(ef)
294 total_data = ''
295 chunk_offset = 0
296 while chunk_offset < data_length:
297 chunk_len = min(255, data_length - chunk_offset)
298 # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
299 pdu = self.cla_byte + \
300 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
301 data[chunk_offset*2: (chunk_offset+chunk_len)*2]
302 try:
303 chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
304 except Exception as e:
305 raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
306 (str_sanitize(str(e)), chunk_offset, chunk_len))
307 total_data += data
308 chunk_offset += chunk_len
309 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200310 self.__verify_binary(ef, data, offset)
Harald Weltec91085e2022-02-10 18:05:45 +0100311 return total_data, chunk_sw
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200312
Harald Weltefdb187d2023-07-09 17:03:17 +0200313 def read_record(self, ef: Path, rec_no: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100314 """Execute READ RECORD.
Harald Welteee3501f2021-04-02 13:00:18 +0200315
Harald Weltec91085e2022-02-10 18:05:45 +0100316 Args:
317 ef : string or list of strings indicating name or path of linear fixed EF
318 rec_no : record number to read
319 """
320 r = self.select_path(ef)
321 rec_length = self.__record_len(r)
322 pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
323 return self._tp.send_apdu_checksw(pdu)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100324
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200325 def __verify_record(self, ef: Path, rec_no: int, data: str):
326 """Verify record against given data
327
328 Args:
329 ef : string or list of strings indicating name or path of linear fixed EF
330 rec_no : record number to read
331 data : hex string of data to be verified
332 """
333 res = self.read_record(ef, rec_no)
334 if res[0].lower() != data.lower():
335 raise ValueError('Record verification failed (expected %s, got %s)' % (
336 data.lower(), res[0].lower()))
337
Harald Weltefdb187d2023-07-09 17:03:17 +0200338 def update_record(self, ef: Path, rec_no: int, data: Hexstr, force_len: bool = False,
Philipp Maier37e57e02023-09-07 12:43:12 +0200339 verify: bool = False, conserve: bool = False, leftpad: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100340 """Execute UPDATE RECORD.
Philipp Maier712251a2021-11-04 17:09:37 +0100341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 Args:
343 ef : string or list of strings indicating name or path of linear fixed EF
344 rec_no : record number to read
345 data : hex string of data to be written
346 force_len : enforce record length by using the actual data length
347 verify : verify data by re-reading the record
348 conserve : read record and compare it with data, skip write on match
Philipp Maier37e57e02023-09-07 12:43:12 +0200349 leftpad : apply 0xff padding from the left instead from the right side.
Harald Weltec91085e2022-02-10 18:05:45 +0100350 """
Philipp Maier40ea4a42022-06-02 14:45:41 +0200351
Harald Weltec91085e2022-02-10 18:05:45 +0100352 res = self.select_path(ef)
Philipp Maier40ea4a42022-06-02 14:45:41 +0200353 rec_length = self.__record_len(res)
354 data = expand_hex(data, rec_length)
Philipp Maier42804d72021-04-30 11:56:23 +0200355
Harald Weltec91085e2022-02-10 18:05:45 +0100356 if force_len:
357 # enforce the record length by the actual length of the given data input
358 rec_length = len(data) // 2
359 else:
Philipp Maier40ea4a42022-06-02 14:45:41 +0200360 # make sure the input data is padded to the record length using 0xFF.
361 # In cases where the input data exceed we throw an exception.
Harald Weltec91085e2022-02-10 18:05:45 +0100362 if (len(data) // 2 > rec_length):
363 raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
364 rec_length, len(data) // 2))
365 elif (len(data) // 2 < rec_length):
Philipp Maier37e57e02023-09-07 12:43:12 +0200366 if leftpad:
367 data = lpad(data, rec_length * 2)
368 else:
369 data = rpad(data, rec_length * 2)
Philipp Maier38c74f62021-03-17 17:19:52 +0100370
Harald Weltec91085e2022-02-10 18:05:45 +0100371 # Save write cycles by reading+comparing before write
372 if conserve:
Harald Welte1c0a2492023-12-28 15:42:51 +0100373 try:
374 data_current, sw = self.read_record(ef, rec_no)
375 data_current = data_current[0:rec_length*2]
376 if data_current == data:
377 return None, sw
378 except Exception:
379 # cannot read data. This is not a fatal error, as reading is just done to
380 # conserve the amount of smart card writes. The access conditions of the file
381 # may well permit us to UPDATE but not permit us to READ. So let's ignore
382 # any such exception during READ.
383 pass
Philipp Maier38c74f62021-03-17 17:19:52 +0100384
Harald Weltec91085e2022-02-10 18:05:45 +0100385 pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data
386 res = self._tp.send_apdu_checksw(pdu)
387 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200388 self.__verify_record(ef, rec_no, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100389 return res
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200390
Harald Weltefdb187d2023-07-09 17:03:17 +0200391 def record_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100392 """Determine the record size of given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200393
Harald Weltec91085e2022-02-10 18:05:45 +0100394 Args:
395 ef : string or list of strings indicating name or path of linear fixed EF
396 """
397 r = self.select_path(ef)
398 return self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100399
Harald Weltefdb187d2023-07-09 17:03:17 +0200400 def record_count(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100401 """Determine the number of records in given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200402
Harald Weltec91085e2022-02-10 18:05:45 +0100403 Args:
404 ef : string or list of strings indicating name or path of linear fixed EF
405 """
406 r = self.select_path(ef)
407 return self.__len(r) // self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100408
Harald Weltefdb187d2023-07-09 17:03:17 +0200409 def binary_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100410 """Determine the size of given transparent file.
411
412 Args:
413 ef : string or list of strings indicating name or path of transparent EF
414 """
415 r = self.select_path(ef)
416 return self.__len(r)
Harald Welteee3501f2021-04-02 13:00:18 +0200417
Harald Weltec91085e2022-02-10 18:05:45 +0100418 # TS 102 221 Section 11.3.1 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200419 def _retrieve_data(self, tag: int, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100420 if first:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200421 pdu = self.cla4lchan('80') + 'cb008001%02x' % (tag)
Harald Weltec91085e2022-02-10 18:05:45 +0100422 else:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200423 pdu = self.cla4lchan('80') + 'cb000000'
Harald Weltec91085e2022-02-10 18:05:45 +0100424 return self._tp.send_apdu_checksw(pdu)
Philipp Maier32daaf52020-05-11 21:48:33 +0200425
Harald Weltefdb187d2023-07-09 17:03:17 +0200426 def retrieve_data(self, ef: Path, tag: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100427 """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1.
Harald Welte917d98c2021-04-21 11:51:25 +0200428
Harald Weltec91085e2022-02-10 18:05:45 +0100429 Args
430 ef : string or list of strings indicating name or path of transparent EF
431 tag : BER-TLV Tag of value to be retrieved
432 """
433 r = self.select_path(ef)
434 if len(r[-1]) == 0:
435 return (None, None)
436 total_data = ''
437 # retrieve first block
438 data, sw = self._retrieve_data(tag, first=True)
439 total_data += data
440 while sw == '62f1' or sw == '62f2':
441 data, sw = self._retrieve_data(tag, first=False)
442 total_data += data
443 return total_data, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200444
Harald Weltec91085e2022-02-10 18:05:45 +0100445 # TS 102 221 Section 11.3.2 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200446 def _set_data(self, data: Hexstr, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100447 if first:
448 p1 = 0x80
449 else:
450 p1 = 0x00
451 if isinstance(data, bytes) or isinstance(data, bytearray):
452 data = b2h(data)
Harald Welte3dfab9d2023-10-21 23:38:32 +0200453 pdu = self.cla4lchan('80') + 'db00%02x%02x%s' % (p1, len(data)//2, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100454 return self._tp.send_apdu_checksw(pdu)
Harald Welte917d98c2021-04-21 11:51:25 +0200455
Harald Weltefdb187d2023-07-09 17:03:17 +0200456 def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100457 """Execute SET DATA.
Harald Welte917d98c2021-04-21 11:51:25 +0200458
Harald Weltec91085e2022-02-10 18:05:45 +0100459 Args
460 ef : string or list of strings indicating name or path of transparent EF
461 tag : BER-TLV Tag of value to be stored
462 value : BER-TLV value to be stored
463 """
464 r = self.select_path(ef)
465 if len(r[-1]) == 0:
466 return (None, None)
Harald Welte917d98c2021-04-21 11:51:25 +0200467
Harald Weltec91085e2022-02-10 18:05:45 +0100468 # in case of deleting the data, we only have 'tag' but no 'value'
469 if not value:
470 return self._set_data('%02x' % tag, first=True)
Harald Welte917d98c2021-04-21 11:51:25 +0200471
Harald Weltec91085e2022-02-10 18:05:45 +0100472 # FIXME: proper BER-TLV encode
473 tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2)))
474 tlv = tl + value
475 tlv_bin = h2b(tlv)
Harald Welte917d98c2021-04-21 11:51:25 +0200476
Harald Weltec91085e2022-02-10 18:05:45 +0100477 first = True
478 total_len = len(tlv_bin)
479 remaining = tlv_bin
480 while len(remaining) > 0:
481 fragment = remaining[:255]
482 rdata, sw = self._set_data(fragment, first=first)
483 first = False
484 remaining = remaining[255:]
485 return rdata, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200486
Harald Weltefdb187d2023-07-09 17:03:17 +0200487 def run_gsm(self, rand: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100488 """Execute RUN GSM ALGORITHM.
Harald Welte917d98c2021-04-21 11:51:25 +0200489
Harald Weltec91085e2022-02-10 18:05:45 +0100490 Args:
491 rand : 16 byte random data as hex string (RAND)
492 """
493 if len(rand) != 32:
494 raise ValueError('Invalid rand')
495 self.select_path(['3f00', '7f20'])
Harald Welte3dfab9d2023-10-21 23:38:32 +0200496 return self._tp.send_apdu_checksw(self.cla4lchan('a0') + '88000010' + rand, sw='9000')
Philipp Maier712251a2021-11-04 17:09:37 +0100497
Harald Weltefdb187d2023-07-09 17:03:17 +0200498 def authenticate(self, rand: Hexstr, autn: Hexstr, context: str = '3g') -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100499 """Execute AUTHENTICATE (USIM/ISIM).
Sylvain Munaut76504e02010-12-07 00:24:32 +0100500
Harald Weltec91085e2022-02-10 18:05:45 +0100501 Args:
502 rand : 16 byte random data as hex string (RAND)
503 autn : 8 byte Autentication Token (AUTN)
504 context : 16 byte random data ('3g' or 'gsm')
505 """
506 # 3GPP TS 31.102 Section 7.1.2.1
507 AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
508 AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
509 AuthResp3GSuccess = Struct(
510 Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
511 AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
512 # build parameters
513 cmd_data = {'rand': rand, 'autn': autn}
514 if context == '3g':
515 p2 = '81'
516 elif context == 'gsm':
517 p2 = '80'
518 (data, sw) = self._tp.send_apdu_constr_checksw(
519 self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
520 if 'auts' in data:
521 ret = {'synchronisation_failure': data}
522 else:
523 ret = {'successful_3g_authentication': data}
524 return (ret, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100525
Harald Weltefdb187d2023-07-09 17:03:17 +0200526 def status(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100527 """Execute a STATUS command as per TS 102 221 Section 11.1.2."""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200528 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'F20000ff')
Harald Welte15fae982021-04-10 10:22:27 +0200529
Harald Weltefdb187d2023-07-09 17:03:17 +0200530 def deactivate_file(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100531 """Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14."""
532 return self._tp.send_apdu_constr_checksw(self.cla_byte, '04', '00', '00', None, None, None)
Harald Welte34b05d32021-05-25 22:03:13 +0200533
Harald Weltefdb187d2023-07-09 17:03:17 +0200534 def activate_file(self, fid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100535 """Execute ACTIVATE FILE command as per TS 102 221 Section 11.1.15.
Harald Weltea4631612021-04-10 18:17:55 +0200536
Harald Weltec91085e2022-02-10 18:05:45 +0100537 Args:
538 fid : file identifier as hex string
539 """
540 return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid)
Philipp Maier712251a2021-11-04 17:09:37 +0100541
Harald Weltefdb187d2023-07-09 17:03:17 +0200542 def create_file(self, payload: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200543 """Execute CREEATE FILE command as per TS 102 222 Section 6.3"""
544 return self._tp.send_apdu_checksw(self.cla_byte + 'e00000%02x%s' % (len(payload)//2, payload))
545
Harald Weltefdb187d2023-07-09 17:03:17 +0200546 def resize_file(self, payload: Hexstr) -> ResTuple:
Harald Welte0707b802023-03-07 11:43:37 +0100547 """Execute RESIZE FILE command as per TS 102 222 Section 6.10"""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200548 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'd40000%02x%s' % (len(payload)//2, payload))
Harald Welte0707b802023-03-07 11:43:37 +0100549
Harald Weltefdb187d2023-07-09 17:03:17 +0200550 def delete_file(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200551 """Execute DELETE FILE command as per TS 102 222 Section 6.4"""
552 return self._tp.send_apdu_checksw(self.cla_byte + 'e4000002' + fid)
553
Harald Weltefdb187d2023-07-09 17:03:17 +0200554 def terminate_df(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200555 """Execute TERMINATE DF command as per TS 102 222 Section 6.7"""
556 return self._tp.send_apdu_checksw(self.cla_byte + 'e6000002' + fid)
557
Harald Weltefdb187d2023-07-09 17:03:17 +0200558 def terminate_ef(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200559 """Execute TERMINATE EF command as per TS 102 222 Section 6.8"""
560 return self._tp.send_apdu_checksw(self.cla_byte + 'e8000002' + fid)
561
Harald Weltefdb187d2023-07-09 17:03:17 +0200562 def terminate_card_usage(self) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200563 """Execute TERMINATE CARD USAGE command as per TS 102 222 Section 6.9"""
564 return self._tp.send_apdu_checksw(self.cla_byte + 'fe000000')
565
Harald Weltefdb187d2023-07-09 17:03:17 +0200566 def manage_channel(self, mode: str = 'open', lchan_nr: int =0) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100567 """Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17.
Harald Weltea4631612021-04-10 18:17:55 +0200568
Harald Weltec91085e2022-02-10 18:05:45 +0100569 Args:
570 mode : logical channel operation code ('open' or 'close')
571 lchan_nr : logical channel number (1-19, 0=assigned by UICC)
572 """
573 if mode == 'close':
574 p1 = 0x80
575 else:
576 p1 = 0x00
577 pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr)
578 return self._tp.send_apdu_checksw(pdu)
Philipp Maier712251a2021-11-04 17:09:37 +0100579
Harald Weltefdb187d2023-07-09 17:03:17 +0200580 def reset_card(self) -> Hexstr:
Harald Weltec91085e2022-02-10 18:05:45 +0100581 """Physically reset the card"""
582 return self._tp.reset_card()
Harald Welte703f9332021-04-10 18:39:32 +0200583
Harald Weltefdb187d2023-07-09 17:03:17 +0200584 def _chv_process_sw(self, op_name: str, chv_no: int, pin_code: Hexstr, sw: SwHexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100585 if sw_match(sw, '63cx'):
586 raise RuntimeError('Failed to %s chv_no 0x%02X with code 0x%s, %i tries left.' %
587 (op_name, chv_no, b2h(pin_code).upper(), int(sw[3])))
588 elif (sw != '9000'):
589 raise SwMatchError(sw, '9000')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100590
Harald Weltefdb187d2023-07-09 17:03:17 +0200591 def verify_chv(self, chv_no: int, code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100592 """Verify a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100593
Harald Weltec91085e2022-02-10 18:05:45 +0100594 Args:
595 chv_no : chv number (1=CHV1, 2=CHV2, ...)
596 code : chv code as hex string
597 """
598 fc = rpad(b2h(code), 16)
599 data, sw = self._tp.send_apdu(
600 self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
601 self._chv_process_sw('verify', chv_no, code, sw)
602 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100603
Harald Weltec91085e2022-02-10 18:05:45 +0100604 def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str):
605 """Unblock a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100606
Harald Weltec91085e2022-02-10 18:05:45 +0100607 Args:
608 chv_no : chv number (1=CHV1, 2=CHV2, ...)
609 puk_code : puk code as hex string
610 pin_code : new chv code as hex string
611 """
612 fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
613 data, sw = self._tp.send_apdu(
614 self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
615 self._chv_process_sw('unblock', chv_no, pin_code, sw)
616 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100617
Harald Weltefdb187d2023-07-09 17:03:17 +0200618 def change_chv(self, chv_no: int, pin_code: Hexstr, new_pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100619 """Change a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100620
Harald Weltec91085e2022-02-10 18:05:45 +0100621 Args:
622 chv_no : chv number (1=CHV1, 2=CHV2, ...)
623 pin_code : current chv code as hex string
624 new_pin_code : new chv code as hex string
625 """
626 fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
627 data, sw = self._tp.send_apdu(
628 self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
629 self._chv_process_sw('change', chv_no, pin_code, sw)
630 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100631
Harald Weltefdb187d2023-07-09 17:03:17 +0200632 def disable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100633 """Disable a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100634
Harald Weltec91085e2022-02-10 18:05:45 +0100635 Args:
636 chv_no : chv number (1=CHV1, 2=CHV2, ...)
637 pin_code : current chv code as hex string
638 new_pin_code : new chv code as hex string
639 """
640 fc = rpad(b2h(pin_code), 16)
641 data, sw = self._tp.send_apdu(
642 self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
643 self._chv_process_sw('disable', chv_no, pin_code, sw)
644 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100645
Harald Weltefdb187d2023-07-09 17:03:17 +0200646 def enable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100647 """Enable a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 Args:
650 chv_no : chv number (1=CHV1, 2=CHV2, ...)
651 pin_code : chv code as hex string
652 """
653 fc = rpad(b2h(pin_code), 16)
654 data, sw = self._tp.send_apdu(
655 self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
656 self._chv_process_sw('enable', chv_no, pin_code, sw)
657 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100658
Harald Weltefdb187d2023-07-09 17:03:17 +0200659 def envelope(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100660 """Send one ENVELOPE command to the SIM
Harald Weltef2011662021-05-24 23:19:30 +0200661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 Args:
663 payload : payload as hex string
664 """
665 return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload))
Philipp Maier712251a2021-11-04 17:09:37 +0100666
Harald Weltefdb187d2023-07-09 17:03:17 +0200667 def terminal_profile(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100668 """Send TERMINAL PROFILE to card
Harald Welte846a8982021-10-08 15:47:16 +0200669
Harald Weltec91085e2022-02-10 18:05:45 +0100670 Args:
671 payload : payload as hex string
672 """
673 data_length = len(payload) // 2
674 data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload)
675 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200678 def suspend_uicc(self, min_len_secs: int = 60, max_len_secs: int = 43200) -> Tuple[int, Hexstr, SwHexstr]:
Harald Weltec91085e2022-02-10 18:05:45 +0100679 """Send SUSPEND UICC to the card.
Harald Welteec950532021-10-20 13:09:00 +0200680
Harald Weltec91085e2022-02-10 18:05:45 +0100681 Args:
682 min_len_secs : mimumum suspend time seconds
683 max_len_secs : maximum suspend time seconds
684 """
685 def encode_duration(secs: int) -> Hexstr:
686 if secs >= 10*24*60*60:
687 return '04%02x' % (secs // (10*24*60*60))
688 elif secs >= 24*60*60:
689 return '03%02x' % (secs // (24*60*60))
690 elif secs >= 60*60:
691 return '02%02x' % (secs // (60*60))
692 elif secs >= 60:
693 return '01%02x' % (secs // 60)
694 else:
695 return '00%02x' % secs
Philipp Maier712251a2021-11-04 17:09:37 +0100696
Harald Weltec91085e2022-02-10 18:05:45 +0100697 def decode_duration(enc: Hexstr) -> int:
698 time_unit = enc[:2]
Harald Weltec85ae412023-06-06 09:03:27 +0200699 length = h2i(enc[2:4])[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100700 if time_unit == '04':
701 return length * 10*24*60*60
702 elif time_unit == '03':
703 return length * 24*60*60
704 elif time_unit == '02':
705 return length * 60*60
706 elif time_unit == '01':
707 return length * 60
708 elif time_unit == '00':
709 return length
710 else:
711 raise ValueError('Time unit must be 0x00..0x04')
712 min_dur_enc = encode_duration(min_len_secs)
713 max_dur_enc = encode_duration(max_len_secs)
714 data, sw = self._tp.send_apdu_checksw(
715 '8076000004' + min_dur_enc + max_dur_enc)
716 negotiated_duration_secs = decode_duration(data[:4])
717 resume_token = data[4:]
718 return (negotiated_duration_secs, resume_token, sw)
Harald Welte34eb5042022-02-21 17:19:28 +0100719
Harald Welteb0e0dce2023-06-06 17:21:13 +0200720 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200721 def resume_uicc(self, token: Hexstr) -> ResTuple:
Harald Welteb0e0dce2023-06-06 17:21:13 +0200722 """Send SUSPEND UICC (resume) to the card."""
723 if len(h2b(token)) != 8:
724 raise ValueError("Token must be 8 bytes long")
725 data, sw = self._tp.send_apdu_checksw('8076010008' + token)
Harald Weltefdb187d2023-07-09 17:03:17 +0200726 return (data, sw)
Harald Welteb0e0dce2023-06-06 17:21:13 +0200727
Harald Welte34eb5042022-02-21 17:19:28 +0100728 def get_data(self, tag: int, cla: int = 0x00):
729 data, sw = self._tp.send_apdu('%02xca%04x00' % (cla, tag))
730 return (data, sw)
Harald Welte7ec82232023-06-06 18:15:52 +0200731
732 # TS 31.102 Section 7.5.2
Harald Weltefdb187d2023-07-09 17:03:17 +0200733 def get_identity(self, context: int) -> Tuple[Hexstr, SwHexstr]:
Harald Welte7ec82232023-06-06 18:15:52 +0200734 data, sw = self._tp.send_apdu_checksw('807800%02x00' % (context))
735 return (data, sw)