blob: 3edee98756c86a055d7cdb152618e092326aeeb4 [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
77 return ret
78
79 @property
80 def cla_byte(self) -> Hexstr:
81 """Return the (cached) patched default CLA byte for this card."""
82 return self._cla4lchan
83
84 @cla_byte.setter
85 def cla_byte(self, new_val: Hexstr):
86 """Set the (raw, without lchan) default CLA value for this card."""
87 self._cla_byte = new_val
88 # compute cached result
89 self._cla4lchan = cla_with_lchan(self._cla_byte, self.lchan_nr)
90
91 def cla4lchan(self, cla: Hexstr) -> Hexstr:
92 """Compute the lchan-patched value of the given CLA value. If no CLA
93 value is provided as argument, the lchan-patched version of the SimCardCommands._cla_byte
94 value is used. Most commands will use the latter, while some wish to override it and
95 can pass it as argument here."""
96 if not cla:
97 # return cached result to avoid re-computing this over and over again
98 return self._cla4lchan
99 else:
100 return cla_with_lchan(cla, self.lchan_nr)
Jan Balke14b350f2015-01-26 11:15:25 +0100101
Harald Weltec91085e2022-02-10 18:05:45 +0100102 # Extract a single FCP item from TLV
Harald Weltefdb187d2023-07-09 17:03:17 +0200103 def __parse_fcp(self, fcp: Hexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100104 # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
105 # DF or ADF
106 from pytlv.TLV import TLV
107 tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b',
108 '8c', '80', 'ab', 'c6', '81', '88'])
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200109
Harald Weltec91085e2022-02-10 18:05:45 +0100110 # pytlv is case sensitive!
111 fcp = fcp.lower()
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200112
Harald Weltec91085e2022-02-10 18:05:45 +0100113 if fcp[0:2] != '62':
114 raise ValueError(
115 'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2])
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200116
Harald Weltec91085e2022-02-10 18:05:45 +0100117 # Unfortunately the spec is not very clear if the FCP length is
118 # coded as one or two byte vale, so we have to try it out by
119 # checking if the length of the remaining TLV string matches
120 # what we get in the length field.
121 # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
122 exp_tlv_len = int(fcp[2:4], 16)
123 if len(fcp[4:]) // 2 == exp_tlv_len:
124 skip = 4
125 else:
126 exp_tlv_len = int(fcp[2:6], 16)
127 if len(fcp[4:]) // 2 == exp_tlv_len:
128 skip = 6
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200129
Harald Weltec91085e2022-02-10 18:05:45 +0100130 # Skip FCP tag and length
131 tlv = fcp[skip:]
132 return tlvparser.parse(tlv)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200133
Harald Weltec91085e2022-02-10 18:05:45 +0100134 # Tell the length of a record by the card response
135 # USIMs respond with an FCP template, which is different
136 # from what SIMs responds. See also:
137 # USIM: ETSI TS 102 221, chapter 11.1.1.3 Response Data
138 # SIM: GSM 11.11, chapter 9.2.1 SELECT
139 def __record_len(self, r) -> int:
140 if self.sel_ctrl == "0004":
141 tlv_parsed = self.__parse_fcp(r[-1])
142 file_descriptor = tlv_parsed['82']
143 # See also ETSI TS 102 221, chapter 11.1.1.4.3 File Descriptor
144 return int(file_descriptor[4:8], 16)
145 else:
146 return int(r[-1][28:30], 16)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200147
Harald Weltec91085e2022-02-10 18:05:45 +0100148 # Tell the length of a binary file. See also comment
149 # above.
150 def __len(self, r) -> int:
151 if self.sel_ctrl == "0004":
152 tlv_parsed = self.__parse_fcp(r[-1])
153 return int(tlv_parsed['80'], 16)
154 else:
155 return int(r[-1][4:8], 16)
Philipp Maier0e3fcaa2018-06-13 12:34:03 +0200156
Harald Weltefdb187d2023-07-09 17:03:17 +0200157 def get_atr(self) -> Hexstr:
Harald Weltec91085e2022-02-10 18:05:45 +0100158 """Return the ATR of the currently inserted card."""
159 return self._tp.get_atr()
Alexander Chemerisd2d660a2017-07-18 16:52:25 +0300160
Harald Weltefdb187d2023-07-09 17:03:17 +0200161 def try_select_path(self, dir_list: List[Hexstr]) -> List[ResTuple]:
Harald Weltec91085e2022-02-10 18:05:45 +0100162 """ Try to select a specified path
Philipp Maier712251a2021-11-04 17:09:37 +0100163
Harald Weltec91085e2022-02-10 18:05:45 +0100164 Args:
165 dir_list : list of hex-string FIDs
166 """
Philipp Maier712251a2021-11-04 17:09:37 +0100167
Harald Weltec91085e2022-02-10 18:05:45 +0100168 rv = []
169 if type(dir_list) is not list:
170 dir_list = [dir_list]
171 for i in dir_list:
172 data, sw = self._tp.send_apdu(
173 self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
174 rv.append((data, sw))
175 if sw != '9000':
176 return rv
177 return rv
Harald Welteca673942020-06-03 15:19:40 +0200178
Harald Weltefdb187d2023-07-09 17:03:17 +0200179 def select_path(self, dir_list: Path) -> List[Hexstr]:
Harald Weltec91085e2022-02-10 18:05:45 +0100180 """Execute SELECT for an entire list/path of FIDs.
Harald Welteee3501f2021-04-02 13:00:18 +0200181
Harald Weltec91085e2022-02-10 18:05:45 +0100182 Args:
183 dir_list: list of FIDs representing the path to select
Harald Welteee3501f2021-04-02 13:00:18 +0200184
Harald Weltec91085e2022-02-10 18:05:45 +0100185 Returns:
186 list of return values (FCP in hex encoding) for each element of the path
187 """
188 rv = []
189 if type(dir_list) is not list:
190 dir_list = [dir_list]
191 for i in dir_list:
192 data, sw = self.select_file(i)
193 rv.append(data)
194 return rv
Sylvain Munaut76504e02010-12-07 00:24:32 +0100195
Harald Weltefdb187d2023-07-09 17:03:17 +0200196 def select_file(self, fid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100197 """Execute SELECT a given file by FID.
Philipp Maier712251a2021-11-04 17:09:37 +0100198
Harald Weltec91085e2022-02-10 18:05:45 +0100199 Args:
200 fid : file identifier as hex string
201 """
Philipp Maier712251a2021-11-04 17:09:37 +0100202
Harald Weltec91085e2022-02-10 18:05:45 +0100203 return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid)
Harald Welte85484a92021-01-21 16:08:56 +0100204
Harald Weltefdb187d2023-07-09 17:03:17 +0200205 def select_parent_df(self) -> ResTuple:
Harald Welte3729c472022-02-12 14:36:37 +0100206 """Execute SELECT to switch to the parent DF """
207 return self._tp.send_apdu_checksw(self.cla_byte + "a4030400")
208
Harald Weltefdb187d2023-07-09 17:03:17 +0200209 def select_adf(self, aid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100210 """Execute SELECT a given Applicaiton ADF.
Philipp Maier712251a2021-11-04 17:09:37 +0100211
Harald Weltec91085e2022-02-10 18:05:45 +0100212 Args:
213 aid : application identifier as hex string
214 """
Philipp Maier712251a2021-11-04 17:09:37 +0100215
Harald Weltec91085e2022-02-10 18:05:45 +0100216 aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:]
217 return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid)
Philipp Maier0ad5bcf2019-12-31 17:55:47 +0100218
Harald Weltefdb187d2023-07-09 17:03:17 +0200219 def read_binary(self, ef: Path, length: int = None, offset: int = 0) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100220 """Execute READD BINARY.
Harald Welteee3501f2021-04-02 13:00:18 +0200221
Harald Weltec91085e2022-02-10 18:05:45 +0100222 Args:
223 ef : string or list of strings indicating name or path of transparent EF
224 length : number of bytes to read
225 offset : byte offset in file from which to start reading
226 """
227 r = self.select_path(ef)
228 if len(r[-1]) == 0:
229 return (None, None)
230 if length is None:
231 length = self.__len(r) - offset
232 if length < 0:
233 return (None, None)
Philipp Maiere087f902021-11-03 11:46:05 +0100234
Harald Weltec91085e2022-02-10 18:05:45 +0100235 total_data = ''
236 chunk_offset = 0
237 while chunk_offset < length:
238 chunk_len = min(255, length-chunk_offset)
239 pdu = self.cla_byte + \
240 'b0%04x%02x' % (offset + chunk_offset, chunk_len)
241 try:
242 data, sw = self._tp.send_apdu_checksw(pdu)
243 except Exception as e:
244 raise ValueError('%s, failed to read (offset %d)' %
245 (str_sanitize(str(e)), offset))
246 total_data += data
247 chunk_offset += chunk_len
248 return total_data, sw
Sylvain Munaut76504e02010-12-07 00:24:32 +0100249
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200250 def __verify_binary(self, ef, data: str, offset: int = 0):
251 """Verify contents of transparent EF.
252
253 Args:
254 ef : string or list of strings indicating name or path of transparent EF
255 data : hex string of expected data
256 offset : byte offset in file from which to start verifying
257 """
258 res = self.read_binary(ef, len(data) // 2, offset)
259 if res[0].lower() != data.lower():
260 raise ValueError('Binary verification failed (expected %s, got %s)' % (
261 data.lower(), res[0].lower()))
262
Harald Weltefdb187d2023-07-09 17:03:17 +0200263 def update_binary(self, ef: Path, data: Hexstr, offset: int = 0, verify: bool = False,
264 conserve: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100265 """Execute UPDATE BINARY.
Harald Welteee3501f2021-04-02 13:00:18 +0200266
Harald Weltec91085e2022-02-10 18:05:45 +0100267 Args:
268 ef : string or list of strings indicating name or path of transparent EF
269 data : hex string of data to be written
270 offset : byte offset in file from which to start writing
271 verify : Whether or not to verify data after write
272 """
Philipp Maier40ea4a42022-06-02 14:45:41 +0200273
274 file_len = self.binary_size(ef)
275 data = expand_hex(data, file_len)
276
Harald Weltec91085e2022-02-10 18:05:45 +0100277 data_length = len(data) // 2
Philipp Maier38c74f62021-03-17 17:19:52 +0100278
Harald Weltec91085e2022-02-10 18:05:45 +0100279 # Save write cycles by reading+comparing before write
280 if conserve:
281 data_current, sw = self.read_binary(ef, data_length, offset)
282 if data_current == data:
283 return None, sw
Philipp Maier38c74f62021-03-17 17:19:52 +0100284
Harald Weltec91085e2022-02-10 18:05:45 +0100285 self.select_path(ef)
286 total_data = ''
287 chunk_offset = 0
288 while chunk_offset < data_length:
289 chunk_len = min(255, data_length - chunk_offset)
290 # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
291 pdu = self.cla_byte + \
292 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
293 data[chunk_offset*2: (chunk_offset+chunk_len)*2]
294 try:
295 chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
296 except Exception as e:
297 raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
298 (str_sanitize(str(e)), chunk_offset, chunk_len))
299 total_data += data
300 chunk_offset += chunk_len
301 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200302 self.__verify_binary(ef, data, offset)
Harald Weltec91085e2022-02-10 18:05:45 +0100303 return total_data, chunk_sw
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200304
Harald Weltefdb187d2023-07-09 17:03:17 +0200305 def read_record(self, ef: Path, rec_no: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100306 """Execute READ RECORD.
Harald Welteee3501f2021-04-02 13:00:18 +0200307
Harald Weltec91085e2022-02-10 18:05:45 +0100308 Args:
309 ef : string or list of strings indicating name or path of linear fixed EF
310 rec_no : record number to read
311 """
312 r = self.select_path(ef)
313 rec_length = self.__record_len(r)
314 pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
315 return self._tp.send_apdu_checksw(pdu)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100316
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200317 def __verify_record(self, ef: Path, rec_no: int, data: str):
318 """Verify record against given data
319
320 Args:
321 ef : string or list of strings indicating name or path of linear fixed EF
322 rec_no : record number to read
323 data : hex string of data to be verified
324 """
325 res = self.read_record(ef, rec_no)
326 if res[0].lower() != data.lower():
327 raise ValueError('Record verification failed (expected %s, got %s)' % (
328 data.lower(), res[0].lower()))
329
Harald Weltefdb187d2023-07-09 17:03:17 +0200330 def update_record(self, ef: Path, rec_no: int, data: Hexstr, force_len: bool = False,
Philipp Maier37e57e02023-09-07 12:43:12 +0200331 verify: bool = False, conserve: bool = False, leftpad: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100332 """Execute UPDATE RECORD.
Philipp Maier712251a2021-11-04 17:09:37 +0100333
Harald Weltec91085e2022-02-10 18:05:45 +0100334 Args:
335 ef : string or list of strings indicating name or path of linear fixed EF
336 rec_no : record number to read
337 data : hex string of data to be written
338 force_len : enforce record length by using the actual data length
339 verify : verify data by re-reading the record
340 conserve : read record and compare it with data, skip write on match
Philipp Maier37e57e02023-09-07 12:43:12 +0200341 leftpad : apply 0xff padding from the left instead from the right side.
Harald Weltec91085e2022-02-10 18:05:45 +0100342 """
Philipp Maier40ea4a42022-06-02 14:45:41 +0200343
Harald Weltec91085e2022-02-10 18:05:45 +0100344 res = self.select_path(ef)
Philipp Maier40ea4a42022-06-02 14:45:41 +0200345 rec_length = self.__record_len(res)
346 data = expand_hex(data, rec_length)
Philipp Maier42804d72021-04-30 11:56:23 +0200347
Harald Weltec91085e2022-02-10 18:05:45 +0100348 if force_len:
349 # enforce the record length by the actual length of the given data input
350 rec_length = len(data) // 2
351 else:
Philipp Maier40ea4a42022-06-02 14:45:41 +0200352 # make sure the input data is padded to the record length using 0xFF.
353 # In cases where the input data exceed we throw an exception.
Harald Weltec91085e2022-02-10 18:05:45 +0100354 if (len(data) // 2 > rec_length):
355 raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
356 rec_length, len(data) // 2))
357 elif (len(data) // 2 < rec_length):
Philipp Maier37e57e02023-09-07 12:43:12 +0200358 if leftpad:
359 data = lpad(data, rec_length * 2)
360 else:
361 data = rpad(data, rec_length * 2)
Philipp Maier38c74f62021-03-17 17:19:52 +0100362
Harald Weltec91085e2022-02-10 18:05:45 +0100363 # Save write cycles by reading+comparing before write
364 if conserve:
365 data_current, sw = self.read_record(ef, rec_no)
366 data_current = data_current[0:rec_length*2]
367 if data_current == data:
368 return None, sw
Philipp Maier38c74f62021-03-17 17:19:52 +0100369
Harald Weltec91085e2022-02-10 18:05:45 +0100370 pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data
371 res = self._tp.send_apdu_checksw(pdu)
372 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200373 self.__verify_record(ef, rec_no, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100374 return res
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200375
Harald Weltefdb187d2023-07-09 17:03:17 +0200376 def record_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100377 """Determine the record size of given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200378
Harald Weltec91085e2022-02-10 18:05:45 +0100379 Args:
380 ef : string or list of strings indicating name or path of linear fixed EF
381 """
382 r = self.select_path(ef)
383 return self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100384
Harald Weltefdb187d2023-07-09 17:03:17 +0200385 def record_count(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100386 """Determine the number of records in given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200387
Harald Weltec91085e2022-02-10 18:05:45 +0100388 Args:
389 ef : string or list of strings indicating name or path of linear fixed EF
390 """
391 r = self.select_path(ef)
392 return self.__len(r) // self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100393
Harald Weltefdb187d2023-07-09 17:03:17 +0200394 def binary_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100395 """Determine the size of given transparent file.
396
397 Args:
398 ef : string or list of strings indicating name or path of transparent EF
399 """
400 r = self.select_path(ef)
401 return self.__len(r)
Harald Welteee3501f2021-04-02 13:00:18 +0200402
Harald Weltec91085e2022-02-10 18:05:45 +0100403 # TS 102 221 Section 11.3.1 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200404 def _retrieve_data(self, tag: int, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100405 if first:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200406 pdu = self.cla4lchan('80') + 'cb008001%02x' % (tag)
Harald Weltec91085e2022-02-10 18:05:45 +0100407 else:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200408 pdu = self.cla4lchan('80') + 'cb000000'
Harald Weltec91085e2022-02-10 18:05:45 +0100409 return self._tp.send_apdu_checksw(pdu)
Philipp Maier32daaf52020-05-11 21:48:33 +0200410
Harald Weltefdb187d2023-07-09 17:03:17 +0200411 def retrieve_data(self, ef: Path, tag: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100412 """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1.
Harald Welte917d98c2021-04-21 11:51:25 +0200413
Harald Weltec91085e2022-02-10 18:05:45 +0100414 Args
415 ef : string or list of strings indicating name or path of transparent EF
416 tag : BER-TLV Tag of value to be retrieved
417 """
418 r = self.select_path(ef)
419 if len(r[-1]) == 0:
420 return (None, None)
421 total_data = ''
422 # retrieve first block
423 data, sw = self._retrieve_data(tag, first=True)
424 total_data += data
425 while sw == '62f1' or sw == '62f2':
426 data, sw = self._retrieve_data(tag, first=False)
427 total_data += data
428 return total_data, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200429
Harald Weltec91085e2022-02-10 18:05:45 +0100430 # TS 102 221 Section 11.3.2 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200431 def _set_data(self, data: Hexstr, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100432 if first:
433 p1 = 0x80
434 else:
435 p1 = 0x00
436 if isinstance(data, bytes) or isinstance(data, bytearray):
437 data = b2h(data)
Harald Welte3dfab9d2023-10-21 23:38:32 +0200438 pdu = self.cla4lchan('80') + 'db00%02x%02x%s' % (p1, len(data)//2, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100439 return self._tp.send_apdu_checksw(pdu)
Harald Welte917d98c2021-04-21 11:51:25 +0200440
Harald Weltefdb187d2023-07-09 17:03:17 +0200441 def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100442 """Execute SET DATA.
Harald Welte917d98c2021-04-21 11:51:25 +0200443
Harald Weltec91085e2022-02-10 18:05:45 +0100444 Args
445 ef : string or list of strings indicating name or path of transparent EF
446 tag : BER-TLV Tag of value to be stored
447 value : BER-TLV value to be stored
448 """
449 r = self.select_path(ef)
450 if len(r[-1]) == 0:
451 return (None, None)
Harald Welte917d98c2021-04-21 11:51:25 +0200452
Harald Weltec91085e2022-02-10 18:05:45 +0100453 # in case of deleting the data, we only have 'tag' but no 'value'
454 if not value:
455 return self._set_data('%02x' % tag, first=True)
Harald Welte917d98c2021-04-21 11:51:25 +0200456
Harald Weltec91085e2022-02-10 18:05:45 +0100457 # FIXME: proper BER-TLV encode
458 tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2)))
459 tlv = tl + value
460 tlv_bin = h2b(tlv)
Harald Welte917d98c2021-04-21 11:51:25 +0200461
Harald Weltec91085e2022-02-10 18:05:45 +0100462 first = True
463 total_len = len(tlv_bin)
464 remaining = tlv_bin
465 while len(remaining) > 0:
466 fragment = remaining[:255]
467 rdata, sw = self._set_data(fragment, first=first)
468 first = False
469 remaining = remaining[255:]
470 return rdata, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200471
Harald Weltefdb187d2023-07-09 17:03:17 +0200472 def run_gsm(self, rand: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100473 """Execute RUN GSM ALGORITHM.
Harald Welte917d98c2021-04-21 11:51:25 +0200474
Harald Weltec91085e2022-02-10 18:05:45 +0100475 Args:
476 rand : 16 byte random data as hex string (RAND)
477 """
478 if len(rand) != 32:
479 raise ValueError('Invalid rand')
480 self.select_path(['3f00', '7f20'])
Harald Welte3dfab9d2023-10-21 23:38:32 +0200481 return self._tp.send_apdu_checksw(self.cla4lchan('a0') + '88000010' + rand, sw='9000')
Philipp Maier712251a2021-11-04 17:09:37 +0100482
Harald Weltefdb187d2023-07-09 17:03:17 +0200483 def authenticate(self, rand: Hexstr, autn: Hexstr, context: str = '3g') -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100484 """Execute AUTHENTICATE (USIM/ISIM).
Sylvain Munaut76504e02010-12-07 00:24:32 +0100485
Harald Weltec91085e2022-02-10 18:05:45 +0100486 Args:
487 rand : 16 byte random data as hex string (RAND)
488 autn : 8 byte Autentication Token (AUTN)
489 context : 16 byte random data ('3g' or 'gsm')
490 """
491 # 3GPP TS 31.102 Section 7.1.2.1
492 AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
493 AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
494 AuthResp3GSuccess = Struct(
495 Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
496 AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
497 # build parameters
498 cmd_data = {'rand': rand, 'autn': autn}
499 if context == '3g':
500 p2 = '81'
501 elif context == 'gsm':
502 p2 = '80'
503 (data, sw) = self._tp.send_apdu_constr_checksw(
504 self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
505 if 'auts' in data:
506 ret = {'synchronisation_failure': data}
507 else:
508 ret = {'successful_3g_authentication': data}
509 return (ret, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100510
Harald Weltefdb187d2023-07-09 17:03:17 +0200511 def status(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100512 """Execute a STATUS command as per TS 102 221 Section 11.1.2."""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200513 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'F20000ff')
Harald Welte15fae982021-04-10 10:22:27 +0200514
Harald Weltefdb187d2023-07-09 17:03:17 +0200515 def deactivate_file(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100516 """Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14."""
517 return self._tp.send_apdu_constr_checksw(self.cla_byte, '04', '00', '00', None, None, None)
Harald Welte34b05d32021-05-25 22:03:13 +0200518
Harald Weltefdb187d2023-07-09 17:03:17 +0200519 def activate_file(self, fid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100520 """Execute ACTIVATE FILE command as per TS 102 221 Section 11.1.15.
Harald Weltea4631612021-04-10 18:17:55 +0200521
Harald Weltec91085e2022-02-10 18:05:45 +0100522 Args:
523 fid : file identifier as hex string
524 """
525 return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid)
Philipp Maier712251a2021-11-04 17:09:37 +0100526
Harald Weltefdb187d2023-07-09 17:03:17 +0200527 def create_file(self, payload: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200528 """Execute CREEATE FILE command as per TS 102 222 Section 6.3"""
529 return self._tp.send_apdu_checksw(self.cla_byte + 'e00000%02x%s' % (len(payload)//2, payload))
530
Harald Weltefdb187d2023-07-09 17:03:17 +0200531 def resize_file(self, payload: Hexstr) -> ResTuple:
Harald Welte0707b802023-03-07 11:43:37 +0100532 """Execute RESIZE FILE command as per TS 102 222 Section 6.10"""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200533 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'd40000%02x%s' % (len(payload)//2, payload))
Harald Welte0707b802023-03-07 11:43:37 +0100534
Harald Weltefdb187d2023-07-09 17:03:17 +0200535 def delete_file(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200536 """Execute DELETE FILE command as per TS 102 222 Section 6.4"""
537 return self._tp.send_apdu_checksw(self.cla_byte + 'e4000002' + fid)
538
Harald Weltefdb187d2023-07-09 17:03:17 +0200539 def terminate_df(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200540 """Execute TERMINATE DF command as per TS 102 222 Section 6.7"""
541 return self._tp.send_apdu_checksw(self.cla_byte + 'e6000002' + fid)
542
Harald Weltefdb187d2023-07-09 17:03:17 +0200543 def terminate_ef(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200544 """Execute TERMINATE EF command as per TS 102 222 Section 6.8"""
545 return self._tp.send_apdu_checksw(self.cla_byte + 'e8000002' + fid)
546
Harald Weltefdb187d2023-07-09 17:03:17 +0200547 def terminate_card_usage(self) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200548 """Execute TERMINATE CARD USAGE command as per TS 102 222 Section 6.9"""
549 return self._tp.send_apdu_checksw(self.cla_byte + 'fe000000')
550
Harald Weltefdb187d2023-07-09 17:03:17 +0200551 def manage_channel(self, mode: str = 'open', lchan_nr: int =0) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100552 """Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17.
Harald Weltea4631612021-04-10 18:17:55 +0200553
Harald Weltec91085e2022-02-10 18:05:45 +0100554 Args:
555 mode : logical channel operation code ('open' or 'close')
556 lchan_nr : logical channel number (1-19, 0=assigned by UICC)
557 """
558 if mode == 'close':
559 p1 = 0x80
560 else:
561 p1 = 0x00
562 pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr)
563 return self._tp.send_apdu_checksw(pdu)
Philipp Maier712251a2021-11-04 17:09:37 +0100564
Harald Weltefdb187d2023-07-09 17:03:17 +0200565 def reset_card(self) -> Hexstr:
Harald Weltec91085e2022-02-10 18:05:45 +0100566 """Physically reset the card"""
567 return self._tp.reset_card()
Harald Welte703f9332021-04-10 18:39:32 +0200568
Harald Weltefdb187d2023-07-09 17:03:17 +0200569 def _chv_process_sw(self, op_name: str, chv_no: int, pin_code: Hexstr, sw: SwHexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100570 if sw_match(sw, '63cx'):
571 raise RuntimeError('Failed to %s chv_no 0x%02X with code 0x%s, %i tries left.' %
572 (op_name, chv_no, b2h(pin_code).upper(), int(sw[3])))
573 elif (sw != '9000'):
574 raise SwMatchError(sw, '9000')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100575
Harald Weltefdb187d2023-07-09 17:03:17 +0200576 def verify_chv(self, chv_no: int, code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100577 """Verify a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100578
Harald Weltec91085e2022-02-10 18:05:45 +0100579 Args:
580 chv_no : chv number (1=CHV1, 2=CHV2, ...)
581 code : chv code as hex string
582 """
583 fc = rpad(b2h(code), 16)
584 data, sw = self._tp.send_apdu(
585 self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
586 self._chv_process_sw('verify', chv_no, code, sw)
587 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100588
Harald Weltec91085e2022-02-10 18:05:45 +0100589 def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str):
590 """Unblock a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100591
Harald Weltec91085e2022-02-10 18:05:45 +0100592 Args:
593 chv_no : chv number (1=CHV1, 2=CHV2, ...)
594 puk_code : puk code as hex string
595 pin_code : new chv code as hex string
596 """
597 fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
598 data, sw = self._tp.send_apdu(
599 self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
600 self._chv_process_sw('unblock', chv_no, pin_code, sw)
601 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100602
Harald Weltefdb187d2023-07-09 17:03:17 +0200603 def change_chv(self, chv_no: int, pin_code: Hexstr, new_pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100604 """Change a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100605
Harald Weltec91085e2022-02-10 18:05:45 +0100606 Args:
607 chv_no : chv number (1=CHV1, 2=CHV2, ...)
608 pin_code : current chv code as hex string
609 new_pin_code : new chv code as hex string
610 """
611 fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
612 data, sw = self._tp.send_apdu(
613 self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
614 self._chv_process_sw('change', chv_no, pin_code, sw)
615 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100616
Harald Weltefdb187d2023-07-09 17:03:17 +0200617 def disable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100618 """Disable a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100619
Harald Weltec91085e2022-02-10 18:05:45 +0100620 Args:
621 chv_no : chv number (1=CHV1, 2=CHV2, ...)
622 pin_code : current chv code as hex string
623 new_pin_code : new chv code as hex string
624 """
625 fc = rpad(b2h(pin_code), 16)
626 data, sw = self._tp.send_apdu(
627 self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
628 self._chv_process_sw('disable', chv_no, pin_code, sw)
629 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100630
Harald Weltefdb187d2023-07-09 17:03:17 +0200631 def enable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100632 """Enable a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100633
Harald Weltec91085e2022-02-10 18:05:45 +0100634 Args:
635 chv_no : chv number (1=CHV1, 2=CHV2, ...)
636 pin_code : chv code as hex string
637 """
638 fc = rpad(b2h(pin_code), 16)
639 data, sw = self._tp.send_apdu(
640 self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
641 self._chv_process_sw('enable', chv_no, pin_code, sw)
642 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100643
Harald Weltefdb187d2023-07-09 17:03:17 +0200644 def envelope(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100645 """Send one ENVELOPE command to the SIM
Harald Weltef2011662021-05-24 23:19:30 +0200646
Harald Weltec91085e2022-02-10 18:05:45 +0100647 Args:
648 payload : payload as hex string
649 """
650 return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload))
Philipp Maier712251a2021-11-04 17:09:37 +0100651
Harald Weltefdb187d2023-07-09 17:03:17 +0200652 def terminal_profile(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100653 """Send TERMINAL PROFILE to card
Harald Welte846a8982021-10-08 15:47:16 +0200654
Harald Weltec91085e2022-02-10 18:05:45 +0100655 Args:
656 payload : payload as hex string
657 """
658 data_length = len(payload) // 2
659 data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload)
660 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200663 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 +0100664 """Send SUSPEND UICC to the card.
Harald Welteec950532021-10-20 13:09:00 +0200665
Harald Weltec91085e2022-02-10 18:05:45 +0100666 Args:
667 min_len_secs : mimumum suspend time seconds
668 max_len_secs : maximum suspend time seconds
669 """
670 def encode_duration(secs: int) -> Hexstr:
671 if secs >= 10*24*60*60:
672 return '04%02x' % (secs // (10*24*60*60))
673 elif secs >= 24*60*60:
674 return '03%02x' % (secs // (24*60*60))
675 elif secs >= 60*60:
676 return '02%02x' % (secs // (60*60))
677 elif secs >= 60:
678 return '01%02x' % (secs // 60)
679 else:
680 return '00%02x' % secs
Philipp Maier712251a2021-11-04 17:09:37 +0100681
Harald Weltec91085e2022-02-10 18:05:45 +0100682 def decode_duration(enc: Hexstr) -> int:
683 time_unit = enc[:2]
Harald Weltec85ae412023-06-06 09:03:27 +0200684 length = h2i(enc[2:4])[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100685 if time_unit == '04':
686 return length * 10*24*60*60
687 elif time_unit == '03':
688 return length * 24*60*60
689 elif time_unit == '02':
690 return length * 60*60
691 elif time_unit == '01':
692 return length * 60
693 elif time_unit == '00':
694 return length
695 else:
696 raise ValueError('Time unit must be 0x00..0x04')
697 min_dur_enc = encode_duration(min_len_secs)
698 max_dur_enc = encode_duration(max_len_secs)
699 data, sw = self._tp.send_apdu_checksw(
700 '8076000004' + min_dur_enc + max_dur_enc)
701 negotiated_duration_secs = decode_duration(data[:4])
702 resume_token = data[4:]
703 return (negotiated_duration_secs, resume_token, sw)
Harald Welte34eb5042022-02-21 17:19:28 +0100704
Harald Welteb0e0dce2023-06-06 17:21:13 +0200705 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200706 def resume_uicc(self, token: Hexstr) -> ResTuple:
Harald Welteb0e0dce2023-06-06 17:21:13 +0200707 """Send SUSPEND UICC (resume) to the card."""
708 if len(h2b(token)) != 8:
709 raise ValueError("Token must be 8 bytes long")
710 data, sw = self._tp.send_apdu_checksw('8076010008' + token)
Harald Weltefdb187d2023-07-09 17:03:17 +0200711 return (data, sw)
Harald Welteb0e0dce2023-06-06 17:21:13 +0200712
Harald Welte34eb5042022-02-21 17:19:28 +0100713 def get_data(self, tag: int, cla: int = 0x00):
714 data, sw = self._tp.send_apdu('%02xca%04x00' % (cla, tag))
715 return (data, sw)
Harald Welte7ec82232023-06-06 18:15:52 +0200716
717 # TS 31.102 Section 7.5.2
Harald Weltefdb187d2023-07-09 17:03:17 +0200718 def get_identity(self, context: int) -> Tuple[Hexstr, SwHexstr]:
Harald Welte7ec82232023-06-06 18:15:52 +0200719 data, sw = self._tp.send_apdu_checksw('807800%02x00' % (context))
720 return (data, sw)