blob: 336cf0cc1b9313caf5bd23f4dcc3f193bccee6e5 [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:
282 data_current, sw = self.read_binary(ef, data_length, offset)
283 if data_current == data:
284 return None, sw
Philipp Maier38c74f62021-03-17 17:19:52 +0100285
Harald Weltec91085e2022-02-10 18:05:45 +0100286 self.select_path(ef)
287 total_data = ''
288 chunk_offset = 0
289 while chunk_offset < data_length:
290 chunk_len = min(255, data_length - chunk_offset)
291 # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
292 pdu = self.cla_byte + \
293 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
294 data[chunk_offset*2: (chunk_offset+chunk_len)*2]
295 try:
296 chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
297 except Exception as e:
298 raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
299 (str_sanitize(str(e)), chunk_offset, chunk_len))
300 total_data += data
301 chunk_offset += chunk_len
302 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200303 self.__verify_binary(ef, data, offset)
Harald Weltec91085e2022-02-10 18:05:45 +0100304 return total_data, chunk_sw
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200305
Harald Weltefdb187d2023-07-09 17:03:17 +0200306 def read_record(self, ef: Path, rec_no: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100307 """Execute READ RECORD.
Harald Welteee3501f2021-04-02 13:00:18 +0200308
Harald Weltec91085e2022-02-10 18:05:45 +0100309 Args:
310 ef : string or list of strings indicating name or path of linear fixed EF
311 rec_no : record number to read
312 """
313 r = self.select_path(ef)
314 rec_length = self.__record_len(r)
315 pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
316 return self._tp.send_apdu_checksw(pdu)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100317
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200318 def __verify_record(self, ef: Path, rec_no: int, data: str):
319 """Verify record against given data
320
321 Args:
322 ef : string or list of strings indicating name or path of linear fixed EF
323 rec_no : record number to read
324 data : hex string of data to be verified
325 """
326 res = self.read_record(ef, rec_no)
327 if res[0].lower() != data.lower():
328 raise ValueError('Record verification failed (expected %s, got %s)' % (
329 data.lower(), res[0].lower()))
330
Harald Weltefdb187d2023-07-09 17:03:17 +0200331 def update_record(self, ef: Path, rec_no: int, data: Hexstr, force_len: bool = False,
Philipp Maier37e57e02023-09-07 12:43:12 +0200332 verify: bool = False, conserve: bool = False, leftpad: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100333 """Execute UPDATE RECORD.
Philipp Maier712251a2021-11-04 17:09:37 +0100334
Harald Weltec91085e2022-02-10 18:05:45 +0100335 Args:
336 ef : string or list of strings indicating name or path of linear fixed EF
337 rec_no : record number to read
338 data : hex string of data to be written
339 force_len : enforce record length by using the actual data length
340 verify : verify data by re-reading the record
341 conserve : read record and compare it with data, skip write on match
Philipp Maier37e57e02023-09-07 12:43:12 +0200342 leftpad : apply 0xff padding from the left instead from the right side.
Harald Weltec91085e2022-02-10 18:05:45 +0100343 """
Philipp Maier40ea4a42022-06-02 14:45:41 +0200344
Harald Weltec91085e2022-02-10 18:05:45 +0100345 res = self.select_path(ef)
Philipp Maier40ea4a42022-06-02 14:45:41 +0200346 rec_length = self.__record_len(res)
347 data = expand_hex(data, rec_length)
Philipp Maier42804d72021-04-30 11:56:23 +0200348
Harald Weltec91085e2022-02-10 18:05:45 +0100349 if force_len:
350 # enforce the record length by the actual length of the given data input
351 rec_length = len(data) // 2
352 else:
Philipp Maier40ea4a42022-06-02 14:45:41 +0200353 # make sure the input data is padded to the record length using 0xFF.
354 # In cases where the input data exceed we throw an exception.
Harald Weltec91085e2022-02-10 18:05:45 +0100355 if (len(data) // 2 > rec_length):
356 raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
357 rec_length, len(data) // 2))
358 elif (len(data) // 2 < rec_length):
Philipp Maier37e57e02023-09-07 12:43:12 +0200359 if leftpad:
360 data = lpad(data, rec_length * 2)
361 else:
362 data = rpad(data, rec_length * 2)
Philipp Maier38c74f62021-03-17 17:19:52 +0100363
Harald Weltec91085e2022-02-10 18:05:45 +0100364 # Save write cycles by reading+comparing before write
365 if conserve:
366 data_current, sw = self.read_record(ef, rec_no)
367 data_current = data_current[0:rec_length*2]
368 if data_current == data:
369 return None, sw
Philipp Maier38c74f62021-03-17 17:19:52 +0100370
Harald Weltec91085e2022-02-10 18:05:45 +0100371 pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data
372 res = self._tp.send_apdu_checksw(pdu)
373 if verify:
Philipp Maier0ac4d3c2023-09-07 13:13:43 +0200374 self.__verify_record(ef, rec_no, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100375 return res
Philipp Maier30eb8ca2020-05-11 22:51:37 +0200376
Harald Weltefdb187d2023-07-09 17:03:17 +0200377 def record_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100378 """Determine the record size of given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200379
Harald Weltec91085e2022-02-10 18:05:45 +0100380 Args:
381 ef : string or list of strings indicating name or path of linear fixed EF
382 """
383 r = self.select_path(ef)
384 return self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100385
Harald Weltefdb187d2023-07-09 17:03:17 +0200386 def record_count(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100387 """Determine the number of records in given file.
Harald Welteee3501f2021-04-02 13:00:18 +0200388
Harald Weltec91085e2022-02-10 18:05:45 +0100389 Args:
390 ef : string or list of strings indicating name or path of linear fixed EF
391 """
392 r = self.select_path(ef)
393 return self.__len(r) // self.__record_len(r)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100394
Harald Weltefdb187d2023-07-09 17:03:17 +0200395 def binary_size(self, ef: Path) -> int:
Harald Weltec91085e2022-02-10 18:05:45 +0100396 """Determine the size of given transparent file.
397
398 Args:
399 ef : string or list of strings indicating name or path of transparent EF
400 """
401 r = self.select_path(ef)
402 return self.__len(r)
Harald Welteee3501f2021-04-02 13:00:18 +0200403
Harald Weltec91085e2022-02-10 18:05:45 +0100404 # TS 102 221 Section 11.3.1 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200405 def _retrieve_data(self, tag: int, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100406 if first:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200407 pdu = self.cla4lchan('80') + 'cb008001%02x' % (tag)
Harald Weltec91085e2022-02-10 18:05:45 +0100408 else:
Harald Welte3dfab9d2023-10-21 23:38:32 +0200409 pdu = self.cla4lchan('80') + 'cb000000'
Harald Weltec91085e2022-02-10 18:05:45 +0100410 return self._tp.send_apdu_checksw(pdu)
Philipp Maier32daaf52020-05-11 21:48:33 +0200411
Harald Weltefdb187d2023-07-09 17:03:17 +0200412 def retrieve_data(self, ef: Path, tag: int) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100413 """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1.
Harald Welte917d98c2021-04-21 11:51:25 +0200414
Harald Weltec91085e2022-02-10 18:05:45 +0100415 Args
416 ef : string or list of strings indicating name or path of transparent EF
417 tag : BER-TLV Tag of value to be retrieved
418 """
419 r = self.select_path(ef)
420 if len(r[-1]) == 0:
421 return (None, None)
422 total_data = ''
423 # retrieve first block
424 data, sw = self._retrieve_data(tag, first=True)
425 total_data += data
426 while sw == '62f1' or sw == '62f2':
427 data, sw = self._retrieve_data(tag, first=False)
428 total_data += data
429 return total_data, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200430
Harald Weltec91085e2022-02-10 18:05:45 +0100431 # TS 102 221 Section 11.3.2 low-level helper
Harald Weltefdb187d2023-07-09 17:03:17 +0200432 def _set_data(self, data: Hexstr, first: bool = True) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100433 if first:
434 p1 = 0x80
435 else:
436 p1 = 0x00
437 if isinstance(data, bytes) or isinstance(data, bytearray):
438 data = b2h(data)
Harald Welte3dfab9d2023-10-21 23:38:32 +0200439 pdu = self.cla4lchan('80') + 'db00%02x%02x%s' % (p1, len(data)//2, data)
Harald Weltec91085e2022-02-10 18:05:45 +0100440 return self._tp.send_apdu_checksw(pdu)
Harald Welte917d98c2021-04-21 11:51:25 +0200441
Harald Weltefdb187d2023-07-09 17:03:17 +0200442 def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100443 """Execute SET DATA.
Harald Welte917d98c2021-04-21 11:51:25 +0200444
Harald Weltec91085e2022-02-10 18:05:45 +0100445 Args
446 ef : string or list of strings indicating name or path of transparent EF
447 tag : BER-TLV Tag of value to be stored
448 value : BER-TLV value to be stored
449 """
450 r = self.select_path(ef)
451 if len(r[-1]) == 0:
452 return (None, None)
Harald Welte917d98c2021-04-21 11:51:25 +0200453
Harald Weltec91085e2022-02-10 18:05:45 +0100454 # in case of deleting the data, we only have 'tag' but no 'value'
455 if not value:
456 return self._set_data('%02x' % tag, first=True)
Harald Welte917d98c2021-04-21 11:51:25 +0200457
Harald Weltec91085e2022-02-10 18:05:45 +0100458 # FIXME: proper BER-TLV encode
459 tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2)))
460 tlv = tl + value
461 tlv_bin = h2b(tlv)
Harald Welte917d98c2021-04-21 11:51:25 +0200462
Harald Weltec91085e2022-02-10 18:05:45 +0100463 first = True
464 total_len = len(tlv_bin)
465 remaining = tlv_bin
466 while len(remaining) > 0:
467 fragment = remaining[:255]
468 rdata, sw = self._set_data(fragment, first=first)
469 first = False
470 remaining = remaining[255:]
471 return rdata, sw
Harald Welte917d98c2021-04-21 11:51:25 +0200472
Harald Weltefdb187d2023-07-09 17:03:17 +0200473 def run_gsm(self, rand: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100474 """Execute RUN GSM ALGORITHM.
Harald Welte917d98c2021-04-21 11:51:25 +0200475
Harald Weltec91085e2022-02-10 18:05:45 +0100476 Args:
477 rand : 16 byte random data as hex string (RAND)
478 """
479 if len(rand) != 32:
480 raise ValueError('Invalid rand')
481 self.select_path(['3f00', '7f20'])
Harald Welte3dfab9d2023-10-21 23:38:32 +0200482 return self._tp.send_apdu_checksw(self.cla4lchan('a0') + '88000010' + rand, sw='9000')
Philipp Maier712251a2021-11-04 17:09:37 +0100483
Harald Weltefdb187d2023-07-09 17:03:17 +0200484 def authenticate(self, rand: Hexstr, autn: Hexstr, context: str = '3g') -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100485 """Execute AUTHENTICATE (USIM/ISIM).
Sylvain Munaut76504e02010-12-07 00:24:32 +0100486
Harald Weltec91085e2022-02-10 18:05:45 +0100487 Args:
488 rand : 16 byte random data as hex string (RAND)
489 autn : 8 byte Autentication Token (AUTN)
490 context : 16 byte random data ('3g' or 'gsm')
491 """
492 # 3GPP TS 31.102 Section 7.1.2.1
493 AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
494 AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
495 AuthResp3GSuccess = Struct(
496 Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
497 AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
498 # build parameters
499 cmd_data = {'rand': rand, 'autn': autn}
500 if context == '3g':
501 p2 = '81'
502 elif context == 'gsm':
503 p2 = '80'
504 (data, sw) = self._tp.send_apdu_constr_checksw(
505 self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
506 if 'auts' in data:
507 ret = {'synchronisation_failure': data}
508 else:
509 ret = {'successful_3g_authentication': data}
510 return (ret, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100511
Harald Weltefdb187d2023-07-09 17:03:17 +0200512 def status(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100513 """Execute a STATUS command as per TS 102 221 Section 11.1.2."""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200514 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'F20000ff')
Harald Welte15fae982021-04-10 10:22:27 +0200515
Harald Weltefdb187d2023-07-09 17:03:17 +0200516 def deactivate_file(self) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100517 """Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14."""
518 return self._tp.send_apdu_constr_checksw(self.cla_byte, '04', '00', '00', None, None, None)
Harald Welte34b05d32021-05-25 22:03:13 +0200519
Harald Weltefdb187d2023-07-09 17:03:17 +0200520 def activate_file(self, fid: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100521 """Execute ACTIVATE FILE command as per TS 102 221 Section 11.1.15.
Harald Weltea4631612021-04-10 18:17:55 +0200522
Harald Weltec91085e2022-02-10 18:05:45 +0100523 Args:
524 fid : file identifier as hex string
525 """
526 return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid)
Philipp Maier712251a2021-11-04 17:09:37 +0100527
Harald Weltefdb187d2023-07-09 17:03:17 +0200528 def create_file(self, payload: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200529 """Execute CREEATE FILE command as per TS 102 222 Section 6.3"""
530 return self._tp.send_apdu_checksw(self.cla_byte + 'e00000%02x%s' % (len(payload)//2, payload))
531
Harald Weltefdb187d2023-07-09 17:03:17 +0200532 def resize_file(self, payload: Hexstr) -> ResTuple:
Harald Welte0707b802023-03-07 11:43:37 +0100533 """Execute RESIZE FILE command as per TS 102 222 Section 6.10"""
Harald Welte3dfab9d2023-10-21 23:38:32 +0200534 return self._tp.send_apdu_checksw(self.cla4lchan('80') + 'd40000%02x%s' % (len(payload)//2, payload))
Harald Welte0707b802023-03-07 11:43:37 +0100535
Harald Weltefdb187d2023-07-09 17:03:17 +0200536 def delete_file(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200537 """Execute DELETE FILE command as per TS 102 222 Section 6.4"""
538 return self._tp.send_apdu_checksw(self.cla_byte + 'e4000002' + fid)
539
Harald Weltefdb187d2023-07-09 17:03:17 +0200540 def terminate_df(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200541 """Execute TERMINATE DF command as per TS 102 222 Section 6.7"""
542 return self._tp.send_apdu_checksw(self.cla_byte + 'e6000002' + fid)
543
Harald Weltefdb187d2023-07-09 17:03:17 +0200544 def terminate_ef(self, fid: Hexstr) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200545 """Execute TERMINATE EF command as per TS 102 222 Section 6.8"""
546 return self._tp.send_apdu_checksw(self.cla_byte + 'e8000002' + fid)
547
Harald Weltefdb187d2023-07-09 17:03:17 +0200548 def terminate_card_usage(self) -> ResTuple:
Harald Welte3c9b7842021-10-19 21:44:24 +0200549 """Execute TERMINATE CARD USAGE command as per TS 102 222 Section 6.9"""
550 return self._tp.send_apdu_checksw(self.cla_byte + 'fe000000')
551
Harald Weltefdb187d2023-07-09 17:03:17 +0200552 def manage_channel(self, mode: str = 'open', lchan_nr: int =0) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100553 """Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17.
Harald Weltea4631612021-04-10 18:17:55 +0200554
Harald Weltec91085e2022-02-10 18:05:45 +0100555 Args:
556 mode : logical channel operation code ('open' or 'close')
557 lchan_nr : logical channel number (1-19, 0=assigned by UICC)
558 """
559 if mode == 'close':
560 p1 = 0x80
561 else:
562 p1 = 0x00
563 pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr)
564 return self._tp.send_apdu_checksw(pdu)
Philipp Maier712251a2021-11-04 17:09:37 +0100565
Harald Weltefdb187d2023-07-09 17:03:17 +0200566 def reset_card(self) -> Hexstr:
Harald Weltec91085e2022-02-10 18:05:45 +0100567 """Physically reset the card"""
568 return self._tp.reset_card()
Harald Welte703f9332021-04-10 18:39:32 +0200569
Harald Weltefdb187d2023-07-09 17:03:17 +0200570 def _chv_process_sw(self, op_name: str, chv_no: int, pin_code: Hexstr, sw: SwHexstr):
Harald Weltec91085e2022-02-10 18:05:45 +0100571 if sw_match(sw, '63cx'):
572 raise RuntimeError('Failed to %s chv_no 0x%02X with code 0x%s, %i tries left.' %
573 (op_name, chv_no, b2h(pin_code).upper(), int(sw[3])))
574 elif (sw != '9000'):
575 raise SwMatchError(sw, '9000')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100576
Harald Weltefdb187d2023-07-09 17:03:17 +0200577 def verify_chv(self, chv_no: int, code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100578 """Verify a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100579
Harald Weltec91085e2022-02-10 18:05:45 +0100580 Args:
581 chv_no : chv number (1=CHV1, 2=CHV2, ...)
582 code : chv code as hex string
583 """
584 fc = rpad(b2h(code), 16)
585 data, sw = self._tp.send_apdu(
586 self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
587 self._chv_process_sw('verify', chv_no, code, sw)
588 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100589
Harald Weltec91085e2022-02-10 18:05:45 +0100590 def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str):
591 """Unblock a given CHV (Card Holder Verification == PIN)
Philipp Maier46f09af2021-03-25 20:24:27 +0100592
Harald Weltec91085e2022-02-10 18:05:45 +0100593 Args:
594 chv_no : chv number (1=CHV1, 2=CHV2, ...)
595 puk_code : puk code as hex string
596 pin_code : new chv code as hex string
597 """
598 fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
599 data, sw = self._tp.send_apdu(
600 self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
601 self._chv_process_sw('unblock', chv_no, pin_code, sw)
602 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100603
Harald Weltefdb187d2023-07-09 17:03:17 +0200604 def change_chv(self, chv_no: int, pin_code: Hexstr, new_pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100605 """Change 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 pin_code : current chv code as hex string
610 new_pin_code : new chv code as hex string
611 """
612 fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
613 data, sw = self._tp.send_apdu(
614 self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
615 self._chv_process_sw('change', 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 disable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100619 """Disable 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)
627 data, sw = self._tp.send_apdu(
628 self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
629 self._chv_process_sw('disable', 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 enable_chv(self, chv_no: int, pin_code: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100633 """Enable 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 : chv code as hex string
638 """
639 fc = rpad(b2h(pin_code), 16)
640 data, sw = self._tp.send_apdu(
641 self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
642 self._chv_process_sw('enable', chv_no, pin_code, sw)
643 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100644
Harald Weltefdb187d2023-07-09 17:03:17 +0200645 def envelope(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100646 """Send one ENVELOPE command to the SIM
Harald Weltef2011662021-05-24 23:19:30 +0200647
Harald Weltec91085e2022-02-10 18:05:45 +0100648 Args:
649 payload : payload as hex string
650 """
651 return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload))
Philipp Maier712251a2021-11-04 17:09:37 +0100652
Harald Weltefdb187d2023-07-09 17:03:17 +0200653 def terminal_profile(self, payload: Hexstr) -> ResTuple:
Harald Weltec91085e2022-02-10 18:05:45 +0100654 """Send TERMINAL PROFILE to card
Harald Welte846a8982021-10-08 15:47:16 +0200655
Harald Weltec91085e2022-02-10 18:05:45 +0100656 Args:
657 payload : payload as hex string
658 """
659 data_length = len(payload) // 2
660 data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload)
661 return (data, sw)
Philipp Maier712251a2021-11-04 17:09:37 +0100662
Harald Weltec91085e2022-02-10 18:05:45 +0100663 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200664 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 +0100665 """Send SUSPEND UICC to the card.
Harald Welteec950532021-10-20 13:09:00 +0200666
Harald Weltec91085e2022-02-10 18:05:45 +0100667 Args:
668 min_len_secs : mimumum suspend time seconds
669 max_len_secs : maximum suspend time seconds
670 """
671 def encode_duration(secs: int) -> Hexstr:
672 if secs >= 10*24*60*60:
673 return '04%02x' % (secs // (10*24*60*60))
674 elif secs >= 24*60*60:
675 return '03%02x' % (secs // (24*60*60))
676 elif secs >= 60*60:
677 return '02%02x' % (secs // (60*60))
678 elif secs >= 60:
679 return '01%02x' % (secs // 60)
680 else:
681 return '00%02x' % secs
Philipp Maier712251a2021-11-04 17:09:37 +0100682
Harald Weltec91085e2022-02-10 18:05:45 +0100683 def decode_duration(enc: Hexstr) -> int:
684 time_unit = enc[:2]
Harald Weltec85ae412023-06-06 09:03:27 +0200685 length = h2i(enc[2:4])[0]
Harald Weltec91085e2022-02-10 18:05:45 +0100686 if time_unit == '04':
687 return length * 10*24*60*60
688 elif time_unit == '03':
689 return length * 24*60*60
690 elif time_unit == '02':
691 return length * 60*60
692 elif time_unit == '01':
693 return length * 60
694 elif time_unit == '00':
695 return length
696 else:
697 raise ValueError('Time unit must be 0x00..0x04')
698 min_dur_enc = encode_duration(min_len_secs)
699 max_dur_enc = encode_duration(max_len_secs)
700 data, sw = self._tp.send_apdu_checksw(
701 '8076000004' + min_dur_enc + max_dur_enc)
702 negotiated_duration_secs = decode_duration(data[:4])
703 resume_token = data[4:]
704 return (negotiated_duration_secs, resume_token, sw)
Harald Welte34eb5042022-02-21 17:19:28 +0100705
Harald Welteb0e0dce2023-06-06 17:21:13 +0200706 # ETSI TS 102 221 11.1.22
Harald Weltefdb187d2023-07-09 17:03:17 +0200707 def resume_uicc(self, token: Hexstr) -> ResTuple:
Harald Welteb0e0dce2023-06-06 17:21:13 +0200708 """Send SUSPEND UICC (resume) to the card."""
709 if len(h2b(token)) != 8:
710 raise ValueError("Token must be 8 bytes long")
711 data, sw = self._tp.send_apdu_checksw('8076010008' + token)
Harald Weltefdb187d2023-07-09 17:03:17 +0200712 return (data, sw)
Harald Welteb0e0dce2023-06-06 17:21:13 +0200713
Harald Welte34eb5042022-02-21 17:19:28 +0100714 def get_data(self, tag: int, cla: int = 0x00):
715 data, sw = self._tp.send_apdu('%02xca%04x00' % (cla, tag))
716 return (data, sw)
Harald Welte7ec82232023-06-06 18:15:52 +0200717
718 # TS 31.102 Section 7.5.2
Harald Weltefdb187d2023-07-09 17:03:17 +0200719 def get_identity(self, context: int) -> Tuple[Hexstr, SwHexstr]:
Harald Welte7ec82232023-06-06 18:15:52 +0200720 data, sw = self._tp.send_apdu_checksw('807800%02x00' % (context))
721 return (data, sw)