blob: da4eb93b9bb0cc32ee2c30d7be4bec4fd3d964a6 [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001# coding=utf-8
2"""Utilities / Functions related to ETSI TS 102 221, the core UICC spec.
3
4(C) 2021 by Harald Welte <laforge@osmocom.org>
5
6This program is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 2 of the License, or
9(at your option) any later version.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program. If not, see <http://www.gnu.org/licenses/>.
18"""
19
20from pytlv.TLV import *
Harald Welte8f892fb2021-06-05 10:12:43 +020021from construct import *
22from pySim.construct import *
Harald Welteb2edd142021-01-08 23:29:35 +010023from pySim.utils import *
24from pySim.filesystem import *
Harald Welte181c7c52022-02-10 14:18:32 +010025from pySim.tlv import *
Harald Welte4ae228a2021-05-02 21:29:04 +020026from bidict import bidict
Philipp Maiera028c7d2021-11-08 16:12:03 +010027from pySim.profile import CardProfile
28from pySim.profile import match_uicc
29from pySim.profile import match_sim
Harald Welte181c7c52022-02-10 14:18:32 +010030import pySim.iso7816_4 as iso7816_4
Philipp Maiera028c7d2021-11-08 16:12:03 +010031
32# A UICC will usually also support 2G functionality. If this is the case, we
33# need to add DF_GSM and DF_TELECOM along with the UICC related files
34from pySim.ts_51_011 import DF_GSM, DF_TELECOM
Harald Welte4ae228a2021-05-02 21:29:04 +020035
36ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
37 # TS 102 221 Section 10.1.2 Table 10.5 "Coding of Instruction Byte"
38 CardCommand('SELECT', 0xA4, ['0X', '4X', '6X']),
39 CardCommand('STATUS', 0xF2, ['8X', 'CX', 'EX']),
40 CardCommand('READ BINARY', 0xB0, ['0X', '4X', '6X']),
41 CardCommand('UPDATE BINARY', 0xD6, ['0X', '4X', '6X']),
42 CardCommand('READ RECORD', 0xB2, ['0X', '4X', '6X']),
43 CardCommand('UPDATE RECORD', 0xDC, ['0X', '4X', '6X']),
44 CardCommand('SEARCH RECORD', 0xA2, ['0X', '4X', '6X']),
45 CardCommand('INCREASE', 0x32, ['8X', 'CX', 'EX']),
46 CardCommand('RETRIEVE DATA', 0xCB, ['8X', 'CX', 'EX']),
47 CardCommand('SET DATA', 0xDB, ['8X', 'CX', 'EX']),
48 CardCommand('VERIFY PIN', 0x20, ['0X', '4X', '6X']),
49 CardCommand('CHANGE PIN', 0x24, ['0X', '4X', '6X']),
50 CardCommand('DISABLE PIN', 0x26, ['0X', '4X', '6X']),
51 CardCommand('ENABLE PIN', 0x28, ['0X', '4X', '6X']),
52 CardCommand('UNBLOCK PIN', 0x2C, ['0X', '4X', '6X']),
53 CardCommand('DEACTIVATE FILE', 0x04, ['0X', '4X', '6X']),
54 CardCommand('ACTIVATE FILE', 0x44, ['0X', '4X', '6X']),
55 CardCommand('AUTHENTICATE', 0x88, ['0X', '4X', '6X']),
56 CardCommand('AUTHENTICATE', 0x89, ['0X', '4X', '6X']),
57 CardCommand('GET CHALLENGE', 0x84, ['0X', '4X', '6X']),
58 CardCommand('TERMINAL CAPABILITY', 0xAA, ['8X', 'CX', 'EX']),
59 CardCommand('TERMINAL PROFILE', 0x10, ['80']),
60 CardCommand('ENVELOPE', 0xC2, ['80']),
61 CardCommand('FETCH', 0x12, ['80']),
62 CardCommand('TERMINAL RESPONSE', 0x14, ['80']),
63 CardCommand('MANAGE CHANNEL', 0x70, ['0X', '4X', '6X']),
64 CardCommand('MANAGE SECURE CHANNEL', 0x73, ['0X', '4X', '6X']),
65 CardCommand('TRANSACT DATA', 0x75, ['0X', '4X', '6X']),
66 CardCommand('SUSPEND UICC', 0x76, ['80']),
67 CardCommand('GET IDENTITY', 0x78, ['8X', 'CX', 'EX']),
68 CardCommand('EXCHANGE CAPABILITIES', 0x7A, ['80']),
69 CardCommand('GET RESPONSE', 0xC0, ['0X', '4X', '6X']),
70 # TS 102 222 Section 6.1 Table 1 "Coding of the commands"
71 CardCommand('CREATE FILE', 0xE0, ['0X', '4X']),
72 CardCommand('DELETE FILE', 0xE4, ['0X', '4X']),
73 CardCommand('DEACTIVATE FILE', 0x04, ['0X', '4X']),
74 CardCommand('ACTIVATE FILE', 0x44, ['0X', '4X']),
75 CardCommand('TERMINATE DF', 0xE6, ['0X', '4X']),
76 CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']),
77 CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
78 CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
Harald Weltec91085e2022-02-10 18:05:45 +010079])
Harald Welteb2edd142021-01-08 23:29:35 +010080
81
82FCP_TLV_MAP = {
83 '82': 'file_descriptor',
84 '83': 'file_identifier',
85 '84': 'df_name',
86 'A5': 'proprietary_info',
87 '8A': 'life_cycle_status_int',
88 '8B': 'security_attrib_ref_expanded',
89 '8C': 'security_attrib_compact',
90 'AB': 'security_attrib_espanded',
91 'C6': 'pin_status_template_do',
92 '80': 'file_size',
93 '81': 'total_file_size',
94 '88': 'short_file_id',
Harald Weltec91085e2022-02-10 18:05:45 +010095}
Harald Welteb2edd142021-01-08 23:29:35 +010096
97# ETSI TS 102 221 11.1.1.4.6
98FCP_Proprietary_TLV_MAP = {
99 '80': 'uicc_characteristics',
100 '81': 'application_power_consumption',
101 '82': 'minimum_app_clock_freq',
102 '83': 'available_memory',
103 '84': 'file_details',
104 '85': 'reserved_file_size',
105 '86': 'maximum_file_size',
106 '87': 'suported_system_commands',
107 '88': 'specific_uicc_env_cond',
108 '89': 'p2p_cat_secured_apdu',
109 # Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1')
Harald Weltec91085e2022-02-10 18:05:45 +0100110}
Harald Welteb2edd142021-01-08 23:29:35 +0100111
112# ETSI TS 102 221 11.1.1.4.3
Harald Weltec91085e2022-02-10 18:05:45 +0100113
114
Harald Welteb2edd142021-01-08 23:29:35 +0100115def interpret_file_descriptor(in_hex):
116 in_bin = h2b(in_hex)
117 out = {}
118 ft_dict = {
119 0: 'working_ef',
120 1: 'internal_ef',
121 7: 'df'
122 }
123 fs_dict = {
124 0: 'no_info_given',
125 1: 'transparent',
126 2: 'linear_fixed',
127 6: 'cyclic',
Harald Weltec91085e2022-02-10 18:05:45 +0100128 0x39: 'ber_tlv',
Harald Welteb2edd142021-01-08 23:29:35 +0100129 }
130 fdb = in_bin[0]
131 ftype = (fdb >> 3) & 7
Harald Welte917d98c2021-04-21 11:51:25 +0200132 if fdb & 0xbf == 0x39:
133 fstruct = 0x39
134 else:
135 fstruct = fdb & 7
Harald Welteb2edd142021-01-08 23:29:35 +0100136 out['shareable'] = True if fdb & 0x40 else False
137 out['file_type'] = ft_dict[ftype] if ftype in ft_dict else ftype
138 out['structure'] = fs_dict[fstruct] if fstruct in fs_dict else fstruct
139 if len(in_bin) >= 5:
140 out['record_len'] = int.from_bytes(in_bin[2:4], 'big')
141 out['num_of_rec'] = int.from_bytes(in_bin[4:5], 'big')
142 return out
143
144# ETSI TS 102 221 11.1.1.4.9
Harald Weltec91085e2022-02-10 18:05:45 +0100145
146
Harald Welteb2edd142021-01-08 23:29:35 +0100147def interpret_life_cycle_sts_int(in_hex):
148 lcsi = int(in_hex, 16)
149 if lcsi == 0x00:
150 return 'no_information'
151 elif lcsi == 0x01:
152 return 'creation'
153 elif lcsi == 0x03:
154 return 'initialization'
155 elif lcsi & 0x05 == 0x05:
156 return 'operational_activated'
157 elif lcsi & 0x05 == 0x04:
158 return 'operational_deactivated'
159 elif lcsi & 0xc0 == 0xc0:
160 return 'termination'
161 else:
162 return in_hex
163
Harald Weltec91085e2022-02-10 18:05:45 +0100164
Harald Welteb2edd142021-01-08 23:29:35 +0100165# ETSI TS 102 221 11.1.1.4.10
166FCP_Pin_Status_TLV_MAP = {
167 '90': 'ps_do',
168 '95': 'usage_qualifier',
169 '83': 'key_reference',
Harald Weltec91085e2022-02-10 18:05:45 +0100170}
171
Harald Welteb2edd142021-01-08 23:29:35 +0100172
173def interpret_ps_templ_do(in_hex):
174 # cannot use the 'TLV' parser due to repeating tags
175 #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
Harald Weltec91085e2022-02-10 18:05:45 +0100176 # return psdo_tlv.parse(in_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100177 return in_hex
178
Harald Weltec91085e2022-02-10 18:05:45 +0100179
Harald Welteb2edd142021-01-08 23:29:35 +0100180# 'interpreter' functions for each tag
181FCP_interpreter_map = {
182 '80': lambda x: int(x, 16),
183 '82': interpret_file_descriptor,
184 '8A': interpret_life_cycle_sts_int,
185 'C6': interpret_ps_templ_do,
Harald Weltec91085e2022-02-10 18:05:45 +0100186}
Harald Welteb2edd142021-01-08 23:29:35 +0100187
188FCP_prorietary_interpreter_map = {
189 '83': lambda x: int(x, 16),
Harald Weltec91085e2022-02-10 18:05:45 +0100190}
Harald Welteb2edd142021-01-08 23:29:35 +0100191
192# pytlv unfortunately doesn't have a setting using which we can make it
193# accept unknown tags. It also doesn't raise a specific exception type but
194# just the generic ValueError, so we cannot ignore those either. Instead,
195# we insert a dict entry for every possible proprietary tag permitted
Harald Weltec91085e2022-02-10 18:05:45 +0100196
197
Harald Welteb2edd142021-01-08 23:29:35 +0100198def fixup_fcp_proprietary_tlv_map(tlv_map):
199 if 'D0' in tlv_map:
200 return
Philipp Maierc98ef8a2021-04-07 10:51:22 +0200201 for i in range(0xc0, 0xff):
Harald Welteb2edd142021-01-08 23:29:35 +0100202 i_hex = i2h([i]).upper()
203 tlv_map[i_hex] = 'proprietary_' + i_hex
Robert Falkenberg90f74972021-05-06 09:57:37 +0200204 # Other non-standard TLV objects found on some cards
Harald Weltec91085e2022-02-10 18:05:45 +0100205 tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1
Harald Welteb2edd142021-01-08 23:29:35 +0100206
207
208def tlv_key_replace(inmap, indata):
209 def newkey(inmap, key):
210 if key in inmap:
211 return inmap[key]
212 else:
213 return key
214 return {newkey(inmap, d[0]): d[1] for d in indata.items()}
215
Harald Weltec91085e2022-02-10 18:05:45 +0100216
Harald Welteb2edd142021-01-08 23:29:35 +0100217def tlv_val_interpret(inmap, indata):
218 def newval(inmap, key, val):
219 if key in inmap:
220 return inmap[key](val)
221 else:
222 return val
223 return {d[0]: newval(inmap, d[0], d[1]) for d in indata.items()}
224
Harald Welte4ae228a2021-05-02 21:29:04 +0200225# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
226
Harald Weltec91085e2022-02-10 18:05:45 +0100227
Harald Welte4ae228a2021-05-02 21:29:04 +0200228class _AM_DO_DF(DataObject):
229 def __init__(self):
230 super().__init__('access_mode', 'Access Mode', tag=0x80)
231
Harald Weltec91085e2022-02-10 18:05:45 +0100232 def from_bytes(self, do: bytes):
Harald Welte4ae228a2021-05-02 21:29:04 +0200233 res = []
234 if len(do) != 1:
235 raise ValueError("We only support single-byte AMF inside AM-DO")
236 amf = do[0]
237 # tables 17..29 and 41..44 of 7816-4
238 if amf & 0x80 == 0:
239 if amf & 0x40:
240 res.append('delete_file')
241 if amf & 0x20:
242 res.append('terminate_df')
243 if amf & 0x10:
244 res.append('activate_file')
245 if amf & 0x08:
246 res.append('deactivate_file')
247 if amf & 0x04:
248 res.append('create_file_df')
249 if amf & 0x02:
250 res.append('create_file_ef')
251 if amf & 0x01:
252 res.append('delete_file_child')
253 self.decoded = res
254
255 def to_bytes(self):
256 val = 0
257 if 'delete_file' in self.decoded:
258 val |= 0x40
259 if 'terminate_df' in self.decoded:
260 val |= 0x20
261 if 'activate_file' in self.decoded:
262 val |= 0x10
263 if 'deactivate_file' in self.decoded:
264 val |= 0x08
265 if 'create_file_df' in self.decoded:
266 val |= 0x04
267 if 'create_file_ef' in self.decoded:
268 val |= 0x02
269 if 'delete_file_child' in self.decoded:
270 val |= 0x01
271 return val.to_bytes(1, 'big')
272
273
274class _AM_DO_EF(DataObject):
275 """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
Harald Weltec91085e2022-02-10 18:05:45 +0100276
Harald Welte4ae228a2021-05-02 21:29:04 +0200277 def __init__(self):
278 super().__init__('access_mode', 'Access Mode', tag=0x80)
279
Harald Weltec91085e2022-02-10 18:05:45 +0100280 def from_bytes(self, do: bytes):
Harald Welte4ae228a2021-05-02 21:29:04 +0200281 res = []
282 if len(do) != 1:
283 raise ValueError("We only support single-byte AMF inside AM-DO")
284 amf = do[0]
285 # tables 17..29 and 41..44 of 7816-4
286 if amf & 0x80 == 0:
287 if amf & 0x40:
288 res.append('delete_file')
289 if amf & 0x20:
290 res.append('terminate_ef')
291 if amf & 0x10:
292 res.append('activate_file_or_record')
293 if amf & 0x08:
294 res.append('deactivate_file_or_record')
295 if amf & 0x04:
296 res.append('write_append')
297 if amf & 0x02:
298 res.append('update_erase')
299 if amf & 0x01:
300 res.append('read_search_compare')
301 self.decoded = res
302
303 def to_bytes(self):
304 val = 0
305 if 'delete_file' in self.decoded:
306 val |= 0x40
307 if 'terminate_ef' in self.decoded:
308 val |= 0x20
309 if 'activate_file_or_record' in self.decoded:
310 val |= 0x10
311 if 'deactivate_file_or_record' in self.decoded:
312 val |= 0x08
313 if 'write_append' in self.decoded:
314 val |= 0x04
315 if 'update_erase' in self.decoded:
316 val |= 0x02
317 if 'read_search_compare' in self.decoded:
318 val |= 0x01
319 return val.to_bytes(1, 'big')
320
Harald Weltec91085e2022-02-10 18:05:45 +0100321
Harald Welte4ae228a2021-05-02 21:29:04 +0200322class _AM_DO_CHDR(DataObject):
323 """Command Header Access Mode DO according to ISO 7816-4 Table 32."""
Harald Weltec91085e2022-02-10 18:05:45 +0100324
Harald Welte4ae228a2021-05-02 21:29:04 +0200325 def __init__(self, tag):
326 super().__init__('command_header', 'Command Header Description', tag=tag)
327
Harald Weltec91085e2022-02-10 18:05:45 +0100328 def from_bytes(self, do: bytes):
Harald Welte4ae228a2021-05-02 21:29:04 +0200329 res = {}
330 i = 0
331 if self.tag & 0x08:
332 res['CLA'] = do[i]
333 i += 1
334 if self.tag & 0x04:
335 res['INS'] = do[i]
336 i += 1
337 if self.tag & 0x02:
338 res['P1'] = do[i]
339 i += 1
340 if self.tag & 0x01:
341 res['P2'] = do[i]
342 i += 1
343 self.decoded = res
344
345 def _compute_tag(self):
346 """Override to encode the tag, as it depends on the value."""
347 tag = 0x80
348 if 'CLA' in self.decoded:
349 tag |= 0x08
350 if 'INS' in self.decoded:
351 tag |= 0x04
352 if 'P1' in self.decoded:
353 tag |= 0x02
354 if 'P2' in self.decoded:
355 tag |= 0x01
356 return tag
357
358 def to_bytes(self):
359 res = bytearray()
360 if 'CLA' in self.decoded:
361 res.append(self.decoded['CLA'])
362 if 'INS' in self.decoded:
363 res.append(self.decoded['INS'])
364 if 'P1' in self.decoded:
365 res.append(self.decoded['P1'])
366 if 'P2' in self.decoded:
367 res.append(self.decoded['P2'])
368 return res
369
Harald Weltec91085e2022-02-10 18:05:45 +0100370
Harald Welte4ae228a2021-05-02 21:29:04 +0200371AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
Harald Weltec91085e2022-02-10 18:05:45 +0100372 _AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
373 _AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
374 _AM_DO_CHDR(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c),
375 _AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)])
Harald Welte4ae228a2021-05-02 21:29:04 +0200376
377AM_DO_DF = AM_DO_CHDR | _AM_DO_DF()
378AM_DO_EF = AM_DO_CHDR | _AM_DO_EF()
379
380
381# TS 102 221 Section 9.5.1 / Table 9.3
382pin_names = bidict({
383 0x01: 'PIN1',
384 0x02: 'PIN2',
385 0x03: 'PIN3',
386 0x04: 'PIN4',
387 0x05: 'PIN5',
388 0x06: 'PIN6',
389 0x07: 'PIN7',
390 0x08: 'PIN8',
391 0x0a: 'ADM1',
392 0x0b: 'ADM2',
393 0x0c: 'ADM3',
394 0x0d: 'ADM4',
395 0x0e: 'ADM5',
396
397 0x11: 'UNIVERSAL_PIN',
398 0x81: '2PIN1',
399 0x82: '2PIN2',
400 0x83: '2PIN3',
401 0x84: '2PIN4',
402 0x85: '2PIN5',
403 0x86: '2PIN6',
404 0x87: '2PIN7',
405 0x88: '2PIN8',
406 0x8a: 'ADM6',
407 0x8b: 'ADM7',
408 0x8c: 'ADM8',
409 0x8d: 'ADM9',
410 0x8e: 'ADM10',
Harald Weltec91085e2022-02-10 18:05:45 +0100411})
412
Harald Welte4ae228a2021-05-02 21:29:04 +0200413
414class CRT_DO(DataObject):
415 """Control Reference Template as per TS 102 221 9.5.1"""
Harald Weltec91085e2022-02-10 18:05:45 +0100416
Harald Welte4ae228a2021-05-02 21:29:04 +0200417 def __init__(self):
Harald Weltec91085e2022-02-10 18:05:45 +0100418 super().__init__('control_reference_template',
419 'Control Reference Template', tag=0xA4)
Harald Welte4ae228a2021-05-02 21:29:04 +0200420
421 def from_bytes(self, do: bytes):
422 """Decode a Control Reference Template DO."""
423 if len(do) != 6:
424 raise ValueError('Unsupported CRT DO length: %s', do)
425 if do[0] != 0x83 or do[1] != 0x01:
426 raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
427 if do[3:] != b'\x95\x01\x08':
Harald Weltec91085e2022-02-10 18:05:45 +0100428 raise ValueError(
429 'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
Harald Welte4ae228a2021-05-02 21:29:04 +0200430 self.encoded = do[0:6]
431 self.decoded = pin_names[do[2]]
432 return do[6:]
433
434 def to_bytes(self):
435 pin = pin_names.inverse[self.decoded]
436 return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
437
438# ISO7816-4 9.3.3 Table 33
Harald Weltec91085e2022-02-10 18:05:45 +0100439
440
Harald Welte4ae228a2021-05-02 21:29:04 +0200441class SecCondByte_DO(DataObject):
442 def __init__(self, tag=0x9d):
443 super().__init__('security_condition_byte', tag=tag)
444
Harald Weltec91085e2022-02-10 18:05:45 +0100445 def from_bytes(self, binary: bytes):
Harald Welte4ae228a2021-05-02 21:29:04 +0200446 if len(binary) != 1:
447 raise ValueError
448 inb = binary[0]
449 if inb == 0:
450 cond = 'always'
451 if inb == 0xff:
452 cond = 'never'
453 res = []
454 if inb & 0x80:
455 cond = 'and'
456 else:
457 cond = 'or'
458 if inb & 0x40:
459 res.append('secure_messaging')
460 if inb & 0x20:
461 res.append('external_auth')
462 if inb & 0x10:
463 res.append('user_auth')
Harald Weltec91085e2022-02-10 18:05:45 +0100464 rd = {'mode': cond}
Harald Welte4ae228a2021-05-02 21:29:04 +0200465 if len(res):
466 rd['conditions'] = res
467 self.decoded = rd
468
469 def to_bytes(self):
470 mode = self.decoded['mode']
471 if mode == 'always':
472 res = 0
473 elif mode == 'never':
474 res = 0xff
475 else:
476 res = 0
477 if mode == 'and':
478 res |= 0x80
479 elif mode == 'or':
480 pass
481 else:
482 raise ValueError('Unknown mode %s' % mode)
483 for c in self.decoded['conditions']:
484 if c == 'secure_messaging':
485 res |= 0x40
486 elif c == 'external_auth':
487 res |= 0x20
488 elif c == 'user_auth':
489 res |= 0x10
490 else:
491 raise ValueError('Unknown condition %s' % c)
492 return res.to_bytes(1, 'big')
493
Harald Weltec91085e2022-02-10 18:05:45 +0100494
Harald Welte4ae228a2021-05-02 21:29:04 +0200495Always_DO = TL0_DataObject('always', 'Always', 0x90)
496Never_DO = TL0_DataObject('never', 'Never', 0x97)
Harald Welteb0608332022-02-10 12:45:37 +0100497
Harald Weltec91085e2022-02-10 18:05:45 +0100498
Harald Welteb0608332022-02-10 12:45:37 +0100499class Nested_DO(DataObject):
500 """A DO that nests another DO/Choice/Sequence"""
Harald Weltec91085e2022-02-10 18:05:45 +0100501
Harald Welteb0608332022-02-10 12:45:37 +0100502 def __init__(self, name, tag, choice):
503 super().__init__(name, tag=tag)
504 self.children = choice
Harald Weltec91085e2022-02-10 18:05:45 +0100505
506 def from_bytes(self, binary: bytes) -> list:
Harald Welteb0608332022-02-10 12:45:37 +0100507 remainder = binary
508 self.decoded = []
509 while remainder:
510 rc, remainder = self.children.decode(remainder)
511 self.decoded.append(rc)
512 return self.decoded
Harald Weltec91085e2022-02-10 18:05:45 +0100513
Harald Welteb0608332022-02-10 12:45:37 +0100514 def to_bytes(self) -> bytes:
515 encoded = [self.children.encode(d) for d in self.decoded]
516 return b''.join(encoded)
517
Harald Weltec91085e2022-02-10 18:05:45 +0100518
Harald Welteb0608332022-02-10 12:45:37 +0100519OR_Template = DataObjectChoice('or_template', 'OR-Template',
520 members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
521OR_DO = Nested_DO('or', 0xa0, OR_Template)
522AND_Template = DataObjectChoice('and_template', 'AND-Template',
Harald Weltec91085e2022-02-10 18:05:45 +0100523 members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
Harald Welteb0608332022-02-10 12:45:37 +0100524AND_DO = Nested_DO('and', 0xa7, AND_Template)
525NOT_Template = DataObjectChoice('not_template', 'NOT-Template',
Harald Weltec91085e2022-02-10 18:05:45 +0100526 members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
Harald Welteb0608332022-02-10 12:45:37 +0100527NOT_DO = Nested_DO('not', 0xaf, NOT_Template)
Harald Welte4ae228a2021-05-02 21:29:04 +0200528SC_DO = DataObjectChoice('security_condition', 'Security Condition',
Harald Welteb0608332022-02-10 12:45:37 +0100529 members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO(),
530 OR_DO, AND_DO, NOT_DO])
Harald Welte4ae228a2021-05-02 21:29:04 +0200531
Harald Welteb2edd142021-01-08 23:29:35 +0100532# TS 102 221 Section 13.1
Harald Weltec91085e2022-02-10 18:05:45 +0100533
534
Harald Welteb2edd142021-01-08 23:29:35 +0100535class EF_DIR(LinFixedEF):
Harald Welte181c7c52022-02-10 14:18:32 +0100536 class ApplicationLabel(BER_TLV_IE, tag=0x50):
537 # TODO: UCS-2 coding option as per Annex A of TS 102 221
538 _construct = GreedyString('ascii')
539
540 # see https://github.com/PyCQA/pylint/issues/5794
541 #pylint: disable=undefined-variable
542 class ApplicationTemplate(BER_TLV_IE, tag=0x61,
543 nested=[iso7816_4.ApplicationId, ApplicationLabel, iso7816_4.FileReference,
544 iso7816_4.CommandApdu, iso7816_4.DiscretionaryData,
545 iso7816_4.DiscretionaryTemplate, iso7816_4.URL,
546 iso7816_4.ApplicationRelatedDOSet]):
547 pass
548
Harald Welteb2edd142021-01-08 23:29:35 +0100549 def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'):
Harald Weltec91085e2022-02-10 18:05:45 +0100550 super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5, 54})
Harald Welte181c7c52022-02-10 14:18:32 +0100551 self._tlv = EF_DIR.ApplicationTemplate
Harald Welteb2edd142021-01-08 23:29:35 +0100552
553# TS 102 221 Section 13.2
Harald Weltec91085e2022-02-10 18:05:45 +0100554
555
Harald Welteb2edd142021-01-08 23:29:35 +0100556class EF_ICCID(TransparentEF):
557 def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
Harald Weltec91085e2022-02-10 18:05:45 +0100558 super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10})
Harald Welteb2edd142021-01-08 23:29:35 +0100559
560 def _decode_hex(self, raw_hex):
561 return {'iccid': dec_iccid(raw_hex)}
562
563 def _encode_hex(self, abstract):
564 return enc_iccid(abstract['iccid'])
565
566# TS 102 221 Section 13.3
Harald Weltec91085e2022-02-10 18:05:45 +0100567
568
Harald Welteb2edd142021-01-08 23:29:35 +0100569class EF_PL(TransRecEF):
570 def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
Harald Weltec91085e2022-02-10 18:05:45 +0100571 super().__init__(fid, sfid=sfid, name=name,
572 desc=desc, rec_len=2, size={2, None})
573
Harald Welte0c840f02022-01-21 15:42:22 +0100574 def _decode_record_bin(self, bin_data):
575 if bin_data == b'\xff\xff':
576 return None
577 else:
578 return bin_data.decode('ascii')
Harald Weltec91085e2022-02-10 18:05:45 +0100579
Harald Welte0c840f02022-01-21 15:42:22 +0100580 def _encode_record_bin(self, in_json):
581 if in_json is None:
582 return b'\xff\xff'
583 else:
584 return in_json.encode('ascii')
585
Harald Welteb2edd142021-01-08 23:29:35 +0100586
587# TS 102 221 Section 13.4
588class EF_ARR(LinFixedEF):
589 def __init__(self, fid='2f06', sfid=0x06, name='EF.ARR', desc='Access Rule Reference'):
590 super().__init__(fid, sfid=sfid, name=name, desc=desc)
Harald Welte4ae228a2021-05-02 21:29:04 +0200591 # add those commands to the general commands of a TransparentEF
592 self.shell_commands += [self.AddlShellCommands()]
593
594 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100595 def flatten(inp: list):
Harald Welte4ae228a2021-05-02 21:29:04 +0200596 """Flatten the somewhat deep/complex/nested data returned from decoder."""
597 def sc_abbreviate(sc):
598 if 'always' in sc:
599 return 'always'
600 elif 'never' in sc:
601 return 'never'
602 elif 'control_reference_template' in sc:
603 return sc['control_reference_template']
604 else:
605 return sc
606
607 by_mode = {}
608 for t in inp:
609 am = t[0]
610 sc = t[1]
611 sc_abbr = sc_abbreviate(sc)
612 if 'access_mode' in am:
613 for m in am['access_mode']:
614 by_mode[m] = sc_abbr
615 elif 'command_header' in am:
616 ins = am['command_header']['INS']
617 if 'CLA' in am['command_header']:
618 cla = am['command_header']['CLA']
619 else:
620 cla = None
621 cmd = ts_102_22x_cmdset.lookup(ins, cla)
622 if cmd:
Harald Weltec91085e2022-02-10 18:05:45 +0100623 name = cmd.name.lower().replace(' ', '_')
Harald Welte4ae228a2021-05-02 21:29:04 +0200624 by_mode[name] = sc_abbr
625 else:
626 raise ValueError
627 else:
628 raise ValueError
629 return by_mode
630
631 def _decode_record_bin(self, raw_bin_data):
632 # we can only guess if we should decode for EF or DF here :(
Harald Weltec91085e2022-02-10 18:05:45 +0100633 arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO])
Harald Welte4ae228a2021-05-02 21:29:04 +0200634 dec = arr_seq.decode_multi(raw_bin_data)
635 # we cannot pass the result through flatten() here, as we don't have a related
636 # 'un-flattening' decoder, and hence would be unable to encode :(
637 return dec[0]
638
639 @with_default_category('File-Specific Commands')
640 class AddlShellCommands(CommandSet):
641 def __init__(self):
642 super().__init__()
643
644 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser)
645 def do_read_arr_record(self, opts):
646 """Read one EF.ARR record in flattened, human-friendly form."""
647 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
648 data = self._cmd.rs.selected_file.flatten(data)
649 self._cmd.poutput_json(data, opts.oneline)
650
651 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser)
652 def do_read_arr_records(self, opts):
653 """Read + decode all EF.ARR records in flattened, human-friendly form."""
654 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
655 # collect all results in list so they are rendered as JSON list when printing
656 data_list = []
657 for recnr in range(1, 1 + num_of_rec):
658 (data, sw) = self._cmd.rs.read_record_dec(recnr)
659 data = self._cmd.rs.selected_file.flatten(data)
660 data_list.append(data)
661 self._cmd.poutput_json(data_list, opts.oneline)
662
Harald Welteb2edd142021-01-08 23:29:35 +0100663
664# TS 102 221 Section 13.6
665class EF_UMPC(TransparentEF):
666 def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
Harald Weltec91085e2022-02-10 18:05:45 +0100667 super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5})
668 addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
669 support_uicc_suspend=2)
670 self._construct = Struct(
671 'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
672
Harald Welteb2edd142021-01-08 23:29:35 +0100673
Harald Welteb2edd142021-01-08 23:29:35 +0100674class CardProfileUICC(CardProfile):
Philipp Maiera028c7d2021-11-08 16:12:03 +0100675
676 ORDER = 1
677
Harald Weltec91085e2022-02-10 18:05:45 +0100678 def __init__(self, name='UICC'):
Harald Welteb2edd142021-01-08 23:29:35 +0100679 files = [
680 EF_DIR(),
681 EF_ICCID(),
682 EF_PL(),
683 EF_ARR(),
684 # FIXME: DF.CD
685 EF_UMPC(),
686 ]
687 sw = {
Harald Weltec91085e2022-02-10 18:05:45 +0100688 'Normal': {
689 '9000': 'Normal ending of the command',
690 '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
691 '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
Harald Welteb2edd142021-01-08 23:29:35 +0100692 },
Harald Weltec91085e2022-02-10 18:05:45 +0100693 'Postponed processing': {
694 '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
Harald Welteb2edd142021-01-08 23:29:35 +0100695 },
Harald Weltec91085e2022-02-10 18:05:45 +0100696 'Warnings': {
697 '6200': 'No information given, state of non-volatile memory unchanged',
698 '6281': 'Part of returned data may be corrupted',
699 '6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
700 '6283': 'Selected file invalidated',
701 '6284': 'Selected file in termination state',
702 '62f1': 'More data available',
703 '62f2': 'More data available and proactive command pending',
704 '62f3': 'Response data available',
705 '63f1': 'More data expected',
706 '63f2': 'More data expected and proactive command pending',
707 '63cx': 'Command successful but after using an internal update retry routine X times',
Harald Welteb2edd142021-01-08 23:29:35 +0100708 },
Harald Weltec91085e2022-02-10 18:05:45 +0100709 'Execution errors': {
710 '6400': 'No information given, state of non-volatile memory unchanged',
711 '6500': 'No information given, state of non-volatile memory changed',
712 '6581': 'Memory problem',
Harald Welteb2edd142021-01-08 23:29:35 +0100713 },
Harald Weltec91085e2022-02-10 18:05:45 +0100714 'Checking errors': {
715 '6700': 'Wrong length',
716 '67xx': 'The interpretation of this status word is command dependent',
717 '6b00': 'Wrong parameter(s) P1-P2',
718 '6d00': 'Instruction code not supported or invalid',
719 '6e00': 'Class not supported',
720 '6f00': 'Technical problem, no precise diagnosis',
721 '6fxx': 'The interpretation of this status word is command dependent',
Harald Welteb2edd142021-01-08 23:29:35 +0100722 },
Harald Weltec91085e2022-02-10 18:05:45 +0100723 'Functions in CLA not supported': {
724 '6800': 'No information given',
725 '6881': 'Logical channel not supported',
726 '6882': 'Secure messaging not supported',
Harald Welteb2edd142021-01-08 23:29:35 +0100727 },
Harald Weltec91085e2022-02-10 18:05:45 +0100728 'Command not allowed': {
729 '6900': 'No information given',
730 '6981': 'Command incompatible with file structure',
731 '6982': 'Security status not satisfied',
732 '6983': 'Authentication/PIN method blocked',
733 '6984': 'Referenced data invalidated',
734 '6985': 'Conditions of use not satisfied',
735 '6986': 'Command not allowed (no EF selected)',
736 '6989': 'Command not allowed - secure channel - security not satisfied',
Harald Welteb2edd142021-01-08 23:29:35 +0100737 },
Harald Weltec91085e2022-02-10 18:05:45 +0100738 'Wrong parameters': {
739 '6a80': 'Incorrect parameters in the data field',
740 '6a81': 'Function not supported',
741 '6a82': 'File not found',
742 '6a83': 'Record not found',
743 '6a84': 'Not enough memory space',
744 '6a86': 'Incorrect parameters P1 to P2',
745 '6a87': 'Lc inconsistent with P1 to P2',
746 '6a88': 'Referenced data not found',
Harald Welteb2edd142021-01-08 23:29:35 +0100747 },
Harald Weltec91085e2022-02-10 18:05:45 +0100748 'Application errors': {
749 '9850': 'INCREASE cannot be performed, max value reached',
750 '9862': 'Authentication error, application specific',
751 '9863': 'Security session or association expired',
752 '9864': 'Minimum UICC suspension time is too long',
Harald Welteb2edd142021-01-08 23:29:35 +0100753 },
Harald Weltec91085e2022-02-10 18:05:45 +0100754 }
Harald Welteb2edd142021-01-08 23:29:35 +0100755
Harald Weltec91085e2022-02-10 18:05:45 +0100756 super().__init__(name, desc='ETSI TS 102 221', cla="00",
757 sel_ctrl="0004", files_in_mf=files, sw=sw)
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100758
Philipp Maier5998a3a2021-11-16 15:16:39 +0100759 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100760 def decode_select_response(resp_hex: str) -> object:
Philipp Maier5998a3a2021-11-16 15:16:39 +0100761 """ETSI TS 102 221 Section 11.1.1.3"""
762 fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
763 resp_hex = resp_hex.upper()
764 # outer layer
765 fcp_base_tlv = TLV(['62'])
766 fcp_base = fcp_base_tlv.parse(resp_hex)
767 # actual FCP
768 fcp_tlv = TLV(FCP_TLV_MAP)
769 fcp = fcp_tlv.parse(fcp_base['62'])
770 # further decode the proprietary information
Lennart Rosamc1040952021-11-25 16:19:53 +0100771 if 'A5' in fcp:
Philipp Maier5998a3a2021-11-16 15:16:39 +0100772 prop_tlv = TLV(FCP_Proprietary_TLV_MAP)
773 prop = prop_tlv.parse(fcp['A5'])
774 fcp['A5'] = tlv_val_interpret(FCP_prorietary_interpreter_map, prop)
775 fcp['A5'] = tlv_key_replace(FCP_Proprietary_TLV_MAP, fcp['A5'])
776 # finally make sure we get human-readable keys in the output dict
777 r = tlv_val_interpret(FCP_interpreter_map, fcp)
778 return tlv_key_replace(FCP_TLV_MAP, r)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100779
780 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100781 def match_with_card(scc: SimCardCommands) -> bool:
Philipp Maiera028c7d2021-11-08 16:12:03 +0100782 return match_uicc(scc)
783
Harald Weltec91085e2022-02-10 18:05:45 +0100784
Philipp Maiera028c7d2021-11-08 16:12:03 +0100785class CardProfileUICCSIM(CardProfileUICC):
786 """Same as above, but including 2G SIM support"""
787
788 ORDER = 0
789
790 def __init__(self):
791 super().__init__('UICC-SIM')
792
793 # Add GSM specific files
794 self.files_in_mf.append(DF_TELECOM())
795 self.files_in_mf.append(DF_GSM())
796
797 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +0100798 def match_with_card(scc: SimCardCommands) -> bool:
Philipp Maiera028c7d2021-11-08 16:12:03 +0100799 return match_uicc(scc) and match_sim(scc)