blob: 7cc3025db0779ed0218121bb3cbc8d480b554237 [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 Welte4ae228a2021-05-02 21:29:04 +020025from bidict import bidict
Philipp Maiera028c7d2021-11-08 16:12:03 +010026from pySim.profile import CardProfile
27from pySim.profile import match_uicc
28from pySim.profile import match_sim
29
30# A UICC will usually also support 2G functionality. If this is the case, we
31# need to add DF_GSM and DF_TELECOM along with the UICC related files
32from pySim.ts_51_011 import DF_GSM, DF_TELECOM
Harald Welte4ae228a2021-05-02 21:29:04 +020033
34ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
35 # TS 102 221 Section 10.1.2 Table 10.5 "Coding of Instruction Byte"
36 CardCommand('SELECT', 0xA4, ['0X', '4X', '6X']),
37 CardCommand('STATUS', 0xF2, ['8X', 'CX', 'EX']),
38 CardCommand('READ BINARY', 0xB0, ['0X', '4X', '6X']),
39 CardCommand('UPDATE BINARY', 0xD6, ['0X', '4X', '6X']),
40 CardCommand('READ RECORD', 0xB2, ['0X', '4X', '6X']),
41 CardCommand('UPDATE RECORD', 0xDC, ['0X', '4X', '6X']),
42 CardCommand('SEARCH RECORD', 0xA2, ['0X', '4X', '6X']),
43 CardCommand('INCREASE', 0x32, ['8X', 'CX', 'EX']),
44 CardCommand('RETRIEVE DATA', 0xCB, ['8X', 'CX', 'EX']),
45 CardCommand('SET DATA', 0xDB, ['8X', 'CX', 'EX']),
46 CardCommand('VERIFY PIN', 0x20, ['0X', '4X', '6X']),
47 CardCommand('CHANGE PIN', 0x24, ['0X', '4X', '6X']),
48 CardCommand('DISABLE PIN', 0x26, ['0X', '4X', '6X']),
49 CardCommand('ENABLE PIN', 0x28, ['0X', '4X', '6X']),
50 CardCommand('UNBLOCK PIN', 0x2C, ['0X', '4X', '6X']),
51 CardCommand('DEACTIVATE FILE', 0x04, ['0X', '4X', '6X']),
52 CardCommand('ACTIVATE FILE', 0x44, ['0X', '4X', '6X']),
53 CardCommand('AUTHENTICATE', 0x88, ['0X', '4X', '6X']),
54 CardCommand('AUTHENTICATE', 0x89, ['0X', '4X', '6X']),
55 CardCommand('GET CHALLENGE', 0x84, ['0X', '4X', '6X']),
56 CardCommand('TERMINAL CAPABILITY', 0xAA, ['8X', 'CX', 'EX']),
57 CardCommand('TERMINAL PROFILE', 0x10, ['80']),
58 CardCommand('ENVELOPE', 0xC2, ['80']),
59 CardCommand('FETCH', 0x12, ['80']),
60 CardCommand('TERMINAL RESPONSE', 0x14, ['80']),
61 CardCommand('MANAGE CHANNEL', 0x70, ['0X', '4X', '6X']),
62 CardCommand('MANAGE SECURE CHANNEL', 0x73, ['0X', '4X', '6X']),
63 CardCommand('TRANSACT DATA', 0x75, ['0X', '4X', '6X']),
64 CardCommand('SUSPEND UICC', 0x76, ['80']),
65 CardCommand('GET IDENTITY', 0x78, ['8X', 'CX', 'EX']),
66 CardCommand('EXCHANGE CAPABILITIES', 0x7A, ['80']),
67 CardCommand('GET RESPONSE', 0xC0, ['0X', '4X', '6X']),
68 # TS 102 222 Section 6.1 Table 1 "Coding of the commands"
69 CardCommand('CREATE FILE', 0xE0, ['0X', '4X']),
70 CardCommand('DELETE FILE', 0xE4, ['0X', '4X']),
71 CardCommand('DEACTIVATE FILE', 0x04, ['0X', '4X']),
72 CardCommand('ACTIVATE FILE', 0x44, ['0X', '4X']),
73 CardCommand('TERMINATE DF', 0xE6, ['0X', '4X']),
74 CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']),
75 CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
76 CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
77 ])
Harald Welteb2edd142021-01-08 23:29:35 +010078
79
80FCP_TLV_MAP = {
81 '82': 'file_descriptor',
82 '83': 'file_identifier',
83 '84': 'df_name',
84 'A5': 'proprietary_info',
85 '8A': 'life_cycle_status_int',
86 '8B': 'security_attrib_ref_expanded',
87 '8C': 'security_attrib_compact',
88 'AB': 'security_attrib_espanded',
89 'C6': 'pin_status_template_do',
90 '80': 'file_size',
91 '81': 'total_file_size',
92 '88': 'short_file_id',
93 }
94
95# ETSI TS 102 221 11.1.1.4.6
96FCP_Proprietary_TLV_MAP = {
97 '80': 'uicc_characteristics',
98 '81': 'application_power_consumption',
99 '82': 'minimum_app_clock_freq',
100 '83': 'available_memory',
101 '84': 'file_details',
102 '85': 'reserved_file_size',
103 '86': 'maximum_file_size',
104 '87': 'suported_system_commands',
105 '88': 'specific_uicc_env_cond',
106 '89': 'p2p_cat_secured_apdu',
107 # Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1')
108 }
109
110# ETSI TS 102 221 11.1.1.4.3
111def interpret_file_descriptor(in_hex):
112 in_bin = h2b(in_hex)
113 out = {}
114 ft_dict = {
115 0: 'working_ef',
116 1: 'internal_ef',
117 7: 'df'
118 }
119 fs_dict = {
120 0: 'no_info_given',
121 1: 'transparent',
122 2: 'linear_fixed',
123 6: 'cyclic',
Harald Welte917d98c2021-04-21 11:51:25 +0200124 0x39: 'ber_tlv',
Harald Welteb2edd142021-01-08 23:29:35 +0100125 }
126 fdb = in_bin[0]
127 ftype = (fdb >> 3) & 7
Harald Welte917d98c2021-04-21 11:51:25 +0200128 if fdb & 0xbf == 0x39:
129 fstruct = 0x39
130 else:
131 fstruct = fdb & 7
Harald Welteb2edd142021-01-08 23:29:35 +0100132 out['shareable'] = True if fdb & 0x40 else False
133 out['file_type'] = ft_dict[ftype] if ftype in ft_dict else ftype
134 out['structure'] = fs_dict[fstruct] if fstruct in fs_dict else fstruct
135 if len(in_bin) >= 5:
136 out['record_len'] = int.from_bytes(in_bin[2:4], 'big')
137 out['num_of_rec'] = int.from_bytes(in_bin[4:5], 'big')
138 return out
139
140# ETSI TS 102 221 11.1.1.4.9
141def interpret_life_cycle_sts_int(in_hex):
142 lcsi = int(in_hex, 16)
143 if lcsi == 0x00:
144 return 'no_information'
145 elif lcsi == 0x01:
146 return 'creation'
147 elif lcsi == 0x03:
148 return 'initialization'
149 elif lcsi & 0x05 == 0x05:
150 return 'operational_activated'
151 elif lcsi & 0x05 == 0x04:
152 return 'operational_deactivated'
153 elif lcsi & 0xc0 == 0xc0:
154 return 'termination'
155 else:
156 return in_hex
157
158# ETSI TS 102 221 11.1.1.4.10
159FCP_Pin_Status_TLV_MAP = {
160 '90': 'ps_do',
161 '95': 'usage_qualifier',
162 '83': 'key_reference',
163 }
164
165def interpret_ps_templ_do(in_hex):
166 # cannot use the 'TLV' parser due to repeating tags
167 #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
168 #return psdo_tlv.parse(in_hex)
169 return in_hex
170
171# 'interpreter' functions for each tag
172FCP_interpreter_map = {
173 '80': lambda x: int(x, 16),
174 '82': interpret_file_descriptor,
175 '8A': interpret_life_cycle_sts_int,
176 'C6': interpret_ps_templ_do,
177 }
178
179FCP_prorietary_interpreter_map = {
180 '83': lambda x: int(x, 16),
181 }
182
183# pytlv unfortunately doesn't have a setting using which we can make it
184# accept unknown tags. It also doesn't raise a specific exception type but
185# just the generic ValueError, so we cannot ignore those either. Instead,
186# we insert a dict entry for every possible proprietary tag permitted
187def fixup_fcp_proprietary_tlv_map(tlv_map):
188 if 'D0' in tlv_map:
189 return
Philipp Maierc98ef8a2021-04-07 10:51:22 +0200190 for i in range(0xc0, 0xff):
Harald Welteb2edd142021-01-08 23:29:35 +0100191 i_hex = i2h([i]).upper()
192 tlv_map[i_hex] = 'proprietary_' + i_hex
Robert Falkenberg90f74972021-05-06 09:57:37 +0200193 # Other non-standard TLV objects found on some cards
194 tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1
Harald Welteb2edd142021-01-08 23:29:35 +0100195
196
197def tlv_key_replace(inmap, indata):
198 def newkey(inmap, key):
199 if key in inmap:
200 return inmap[key]
201 else:
202 return key
203 return {newkey(inmap, d[0]): d[1] for d in indata.items()}
204
205def tlv_val_interpret(inmap, indata):
206 def newval(inmap, key, val):
207 if key in inmap:
208 return inmap[key](val)
209 else:
210 return val
211 return {d[0]: newval(inmap, d[0], d[1]) for d in indata.items()}
212
Harald Welte4ae228a2021-05-02 21:29:04 +0200213# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
214
215class _AM_DO_DF(DataObject):
216 def __init__(self):
217 super().__init__('access_mode', 'Access Mode', tag=0x80)
218
219 def from_bytes(self, do:bytes):
220 res = []
221 if len(do) != 1:
222 raise ValueError("We only support single-byte AMF inside AM-DO")
223 amf = do[0]
224 # tables 17..29 and 41..44 of 7816-4
225 if amf & 0x80 == 0:
226 if amf & 0x40:
227 res.append('delete_file')
228 if amf & 0x20:
229 res.append('terminate_df')
230 if amf & 0x10:
231 res.append('activate_file')
232 if amf & 0x08:
233 res.append('deactivate_file')
234 if amf & 0x04:
235 res.append('create_file_df')
236 if amf & 0x02:
237 res.append('create_file_ef')
238 if amf & 0x01:
239 res.append('delete_file_child')
240 self.decoded = res
241
242 def to_bytes(self):
243 val = 0
244 if 'delete_file' in self.decoded:
245 val |= 0x40
246 if 'terminate_df' in self.decoded:
247 val |= 0x20
248 if 'activate_file' in self.decoded:
249 val |= 0x10
250 if 'deactivate_file' in self.decoded:
251 val |= 0x08
252 if 'create_file_df' in self.decoded:
253 val |= 0x04
254 if 'create_file_ef' in self.decoded:
255 val |= 0x02
256 if 'delete_file_child' in self.decoded:
257 val |= 0x01
258 return val.to_bytes(1, 'big')
259
260
261class _AM_DO_EF(DataObject):
262 """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
263 def __init__(self):
264 super().__init__('access_mode', 'Access Mode', tag=0x80)
265
266 def from_bytes(self, do:bytes):
267 res = []
268 if len(do) != 1:
269 raise ValueError("We only support single-byte AMF inside AM-DO")
270 amf = do[0]
271 # tables 17..29 and 41..44 of 7816-4
272 if amf & 0x80 == 0:
273 if amf & 0x40:
274 res.append('delete_file')
275 if amf & 0x20:
276 res.append('terminate_ef')
277 if amf & 0x10:
278 res.append('activate_file_or_record')
279 if amf & 0x08:
280 res.append('deactivate_file_or_record')
281 if amf & 0x04:
282 res.append('write_append')
283 if amf & 0x02:
284 res.append('update_erase')
285 if amf & 0x01:
286 res.append('read_search_compare')
287 self.decoded = res
288
289 def to_bytes(self):
290 val = 0
291 if 'delete_file' in self.decoded:
292 val |= 0x40
293 if 'terminate_ef' in self.decoded:
294 val |= 0x20
295 if 'activate_file_or_record' in self.decoded:
296 val |= 0x10
297 if 'deactivate_file_or_record' in self.decoded:
298 val |= 0x08
299 if 'write_append' in self.decoded:
300 val |= 0x04
301 if 'update_erase' in self.decoded:
302 val |= 0x02
303 if 'read_search_compare' in self.decoded:
304 val |= 0x01
305 return val.to_bytes(1, 'big')
306
307class _AM_DO_CHDR(DataObject):
308 """Command Header Access Mode DO according to ISO 7816-4 Table 32."""
309 def __init__(self, tag):
310 super().__init__('command_header', 'Command Header Description', tag=tag)
311
312 def from_bytes(self, do:bytes):
313 res = {}
314 i = 0
315 if self.tag & 0x08:
316 res['CLA'] = do[i]
317 i += 1
318 if self.tag & 0x04:
319 res['INS'] = do[i]
320 i += 1
321 if self.tag & 0x02:
322 res['P1'] = do[i]
323 i += 1
324 if self.tag & 0x01:
325 res['P2'] = do[i]
326 i += 1
327 self.decoded = res
328
329 def _compute_tag(self):
330 """Override to encode the tag, as it depends on the value."""
331 tag = 0x80
332 if 'CLA' in self.decoded:
333 tag |= 0x08
334 if 'INS' in self.decoded:
335 tag |= 0x04
336 if 'P1' in self.decoded:
337 tag |= 0x02
338 if 'P2' in self.decoded:
339 tag |= 0x01
340 return tag
341
342 def to_bytes(self):
343 res = bytearray()
344 if 'CLA' in self.decoded:
345 res.append(self.decoded['CLA'])
346 if 'INS' in self.decoded:
347 res.append(self.decoded['INS'])
348 if 'P1' in self.decoded:
349 res.append(self.decoded['P1'])
350 if 'P2' in self.decoded:
351 res.append(self.decoded['P2'])
352 return res
353
354AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
355 _AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
356 _AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
357 _AM_DO_CHDR(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c),
358 _AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)])
359
360AM_DO_DF = AM_DO_CHDR | _AM_DO_DF()
361AM_DO_EF = AM_DO_CHDR | _AM_DO_EF()
362
363
364# TS 102 221 Section 9.5.1 / Table 9.3
365pin_names = bidict({
366 0x01: 'PIN1',
367 0x02: 'PIN2',
368 0x03: 'PIN3',
369 0x04: 'PIN4',
370 0x05: 'PIN5',
371 0x06: 'PIN6',
372 0x07: 'PIN7',
373 0x08: 'PIN8',
374 0x0a: 'ADM1',
375 0x0b: 'ADM2',
376 0x0c: 'ADM3',
377 0x0d: 'ADM4',
378 0x0e: 'ADM5',
379
380 0x11: 'UNIVERSAL_PIN',
381 0x81: '2PIN1',
382 0x82: '2PIN2',
383 0x83: '2PIN3',
384 0x84: '2PIN4',
385 0x85: '2PIN5',
386 0x86: '2PIN6',
387 0x87: '2PIN7',
388 0x88: '2PIN8',
389 0x8a: 'ADM6',
390 0x8b: 'ADM7',
391 0x8c: 'ADM8',
392 0x8d: 'ADM9',
393 0x8e: 'ADM10',
394 })
395
396class CRT_DO(DataObject):
397 """Control Reference Template as per TS 102 221 9.5.1"""
398 def __init__(self):
399 super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4)
400
401 def from_bytes(self, do: bytes):
402 """Decode a Control Reference Template DO."""
403 if len(do) != 6:
404 raise ValueError('Unsupported CRT DO length: %s', do)
405 if do[0] != 0x83 or do[1] != 0x01:
406 raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
407 if do[3:] != b'\x95\x01\x08':
408 raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
409 self.encoded = do[0:6]
410 self.decoded = pin_names[do[2]]
411 return do[6:]
412
413 def to_bytes(self):
414 pin = pin_names.inverse[self.decoded]
415 return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
416
417# ISO7816-4 9.3.3 Table 33
418class SecCondByte_DO(DataObject):
419 def __init__(self, tag=0x9d):
420 super().__init__('security_condition_byte', tag=tag)
421
422 def from_bytes(self, binary:bytes):
423 if len(binary) != 1:
424 raise ValueError
425 inb = binary[0]
426 if inb == 0:
427 cond = 'always'
428 if inb == 0xff:
429 cond = 'never'
430 res = []
431 if inb & 0x80:
432 cond = 'and'
433 else:
434 cond = 'or'
435 if inb & 0x40:
436 res.append('secure_messaging')
437 if inb & 0x20:
438 res.append('external_auth')
439 if inb & 0x10:
440 res.append('user_auth')
441 rd = {'mode': cond }
442 if len(res):
443 rd['conditions'] = res
444 self.decoded = rd
445
446 def to_bytes(self):
447 mode = self.decoded['mode']
448 if mode == 'always':
449 res = 0
450 elif mode == 'never':
451 res = 0xff
452 else:
453 res = 0
454 if mode == 'and':
455 res |= 0x80
456 elif mode == 'or':
457 pass
458 else:
459 raise ValueError('Unknown mode %s' % mode)
460 for c in self.decoded['conditions']:
461 if c == 'secure_messaging':
462 res |= 0x40
463 elif c == 'external_auth':
464 res |= 0x20
465 elif c == 'user_auth':
466 res |= 0x10
467 else:
468 raise ValueError('Unknown condition %s' % c)
469 return res.to_bytes(1, 'big')
470
471Always_DO = TL0_DataObject('always', 'Always', 0x90)
472Never_DO = TL0_DataObject('never', 'Never', 0x97)
473SC_DO = DataObjectChoice('security_condition', 'Security Condition',
474 members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
475
Harald Welteb2edd142021-01-08 23:29:35 +0100476# TS 102 221 Section 13.1
477class EF_DIR(LinFixedEF):
478 def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'):
479 super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5,54})
480
481 def _decode_record_hex(self, raw_hex_data):
482 raw_hex_data = raw_hex_data.upper()
483 atempl_base_tlv = TLV(['61'])
484 atempl_base = atempl_base_tlv.parse(raw_hex_data)
485 atempl_TLV_MAP = {'4F': 'aid_value', 50:'label'}
486 atempl_tlv = TLV(atempl_TLV_MAP)
487 atempl = atempl_tlv.parse(atempl_base['61'])
488 # FIXME: "All other Dos are according to ISO/IEC 7816-4"
489 return tlv_key_replace(atempl_TLV_MAP, atempl)
490
491# TS 102 221 Section 13.2
492class EF_ICCID(TransparentEF):
493 def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
494 super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10})
495
496 def _decode_hex(self, raw_hex):
497 return {'iccid': dec_iccid(raw_hex)}
498
499 def _encode_hex(self, abstract):
500 return enc_iccid(abstract['iccid'])
501
502# TS 102 221 Section 13.3
503class EF_PL(TransRecEF):
504 def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
505 super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None})
Harald Welte0c840f02022-01-21 15:42:22 +0100506 def _decode_record_bin(self, bin_data):
507 if bin_data == b'\xff\xff':
508 return None
509 else:
510 return bin_data.decode('ascii')
511 def _encode_record_bin(self, in_json):
512 if in_json is None:
513 return b'\xff\xff'
514 else:
515 return in_json.encode('ascii')
516
Harald Welteb2edd142021-01-08 23:29:35 +0100517
518# TS 102 221 Section 13.4
519class EF_ARR(LinFixedEF):
520 def __init__(self, fid='2f06', sfid=0x06, name='EF.ARR', desc='Access Rule Reference'):
521 super().__init__(fid, sfid=sfid, name=name, desc=desc)
Harald Welte4ae228a2021-05-02 21:29:04 +0200522 # add those commands to the general commands of a TransparentEF
523 self.shell_commands += [self.AddlShellCommands()]
524
525 @staticmethod
526 def flatten(inp:list):
527 """Flatten the somewhat deep/complex/nested data returned from decoder."""
528 def sc_abbreviate(sc):
529 if 'always' in sc:
530 return 'always'
531 elif 'never' in sc:
532 return 'never'
533 elif 'control_reference_template' in sc:
534 return sc['control_reference_template']
535 else:
536 return sc
537
538 by_mode = {}
539 for t in inp:
540 am = t[0]
541 sc = t[1]
542 sc_abbr = sc_abbreviate(sc)
543 if 'access_mode' in am:
544 for m in am['access_mode']:
545 by_mode[m] = sc_abbr
546 elif 'command_header' in am:
547 ins = am['command_header']['INS']
548 if 'CLA' in am['command_header']:
549 cla = am['command_header']['CLA']
550 else:
551 cla = None
552 cmd = ts_102_22x_cmdset.lookup(ins, cla)
553 if cmd:
554 name = cmd.name.lower().replace(' ','_')
555 by_mode[name] = sc_abbr
556 else:
557 raise ValueError
558 else:
559 raise ValueError
560 return by_mode
561
562 def _decode_record_bin(self, raw_bin_data):
563 # we can only guess if we should decode for EF or DF here :(
564 arr_seq = DataObjectSequence('arr', sequence = [AM_DO_EF, SC_DO])
565 dec = arr_seq.decode_multi(raw_bin_data)
566 # we cannot pass the result through flatten() here, as we don't have a related
567 # 'un-flattening' decoder, and hence would be unable to encode :(
568 return dec[0]
569
570 @with_default_category('File-Specific Commands')
571 class AddlShellCommands(CommandSet):
572 def __init__(self):
573 super().__init__()
574
575 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser)
576 def do_read_arr_record(self, opts):
577 """Read one EF.ARR record in flattened, human-friendly form."""
578 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
579 data = self._cmd.rs.selected_file.flatten(data)
580 self._cmd.poutput_json(data, opts.oneline)
581
582 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser)
583 def do_read_arr_records(self, opts):
584 """Read + decode all EF.ARR records in flattened, human-friendly form."""
585 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
586 # collect all results in list so they are rendered as JSON list when printing
587 data_list = []
588 for recnr in range(1, 1 + num_of_rec):
589 (data, sw) = self._cmd.rs.read_record_dec(recnr)
590 data = self._cmd.rs.selected_file.flatten(data)
591 data_list.append(data)
592 self._cmd.poutput_json(data_list, opts.oneline)
593
Harald Welteb2edd142021-01-08 23:29:35 +0100594
595# TS 102 221 Section 13.6
596class EF_UMPC(TransparentEF):
597 def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
598 super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5})
Harald Welte8f892fb2021-06-05 10:12:43 +0200599 addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
600 self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
Harald Welteb2edd142021-01-08 23:29:35 +0100601
Harald Welteb2edd142021-01-08 23:29:35 +0100602class CardProfileUICC(CardProfile):
Philipp Maiera028c7d2021-11-08 16:12:03 +0100603
604 ORDER = 1
605
606 def __init__(self, name = 'UICC'):
Harald Welteb2edd142021-01-08 23:29:35 +0100607 files = [
608 EF_DIR(),
609 EF_ICCID(),
610 EF_PL(),
611 EF_ARR(),
612 # FIXME: DF.CD
613 EF_UMPC(),
614 ]
615 sw = {
616 'Normal': {
617 '9000': 'Normal ending of the command',
618 '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
619 '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
620 },
621 'Postponed processing': {
622 '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
623 },
624 'Warnings': {
625 '6200': 'No information given, state of non-volatile memory unchanged',
626 '6281': 'Part of returned data may be corrupted',
627 '6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
628 '6283': 'Selected file invalidated',
629 '6284': 'Selected file in termination state',
630 '62f1': 'More data available',
631 '62f2': 'More data available and proactive command pending',
632 '62f3': 'Response data available',
633 '63f1': 'More data expected',
634 '63f2': 'More data expected and proactive command pending',
635 '63cx': 'Command successful but after using an internal update retry routine X times',
636 },
637 'Execution errors': {
638 '6400': 'No information given, state of non-volatile memory unchanged',
639 '6500': 'No information given, state of non-volatile memory changed',
640 '6581': 'Memory problem',
641 },
642 'Checking errors': {
643 '6700': 'Wrong length',
644 '67xx': 'The interpretation of this status word is command dependent',
645 '6b00': 'Wrong parameter(s) P1-P2',
646 '6d00': 'Instruction code not supported or invalid',
647 '6e00': 'Class not supported',
648 '6f00': 'Technical problem, no precise diagnosis',
649 '6fxx': 'The interpretation of this status word is command dependent',
650 },
651 'Functions in CLA not supported': {
652 '6800': 'No information given',
653 '6881': 'Logical channel not supported',
654 '6882': 'Secure messaging not supported',
655 },
656 'Command not allowed': {
657 '6900': 'No information given',
658 '6981': 'Command incompatible with file structure',
659 '6982': 'Security status not satisfied',
660 '6983': 'Authentication/PIN method blocked',
661 '6984': 'Referenced data invalidated',
662 '6985': 'Conditions of use not satisfied',
663 '6986': 'Command not allowed (no EF selected)',
664 '6989': 'Command not allowed - secure channel - security not satisfied',
665 },
666 'Wrong parameters': {
667 '6a80': 'Incorrect parameters in the data field',
668 '6a81': 'Function not supported',
669 '6a82': 'File not found',
670 '6a83': 'Record not found',
671 '6a84': 'Not enough memory space',
672 '6a86': 'Incorrect parameters P1 to P2',
673 '6a87': 'Lc inconsistent with P1 to P2',
674 '6a88': 'Referenced data not found',
675 },
676 'Application errors': {
677 '9850': 'INCREASE cannot be performed, max value reached',
678 '9862': 'Authentication error, application specific',
679 '9863': 'Security session or association expired',
680 '9864': 'Minimum UICC suspension time is too long',
681 },
682 }
683
Philipp Maiera028c7d2021-11-08 16:12:03 +0100684 super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw)
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100685
Philipp Maier5998a3a2021-11-16 15:16:39 +0100686 @staticmethod
Philipp Maier9e42e7f2021-11-16 15:46:42 +0100687 def decode_select_response(resp_hex:str) -> object:
Philipp Maier5998a3a2021-11-16 15:16:39 +0100688 """ETSI TS 102 221 Section 11.1.1.3"""
689 fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
690 resp_hex = resp_hex.upper()
691 # outer layer
692 fcp_base_tlv = TLV(['62'])
693 fcp_base = fcp_base_tlv.parse(resp_hex)
694 # actual FCP
695 fcp_tlv = TLV(FCP_TLV_MAP)
696 fcp = fcp_tlv.parse(fcp_base['62'])
697 # further decode the proprietary information
Lennart Rosamc1040952021-11-25 16:19:53 +0100698 if 'A5' in fcp:
Philipp Maier5998a3a2021-11-16 15:16:39 +0100699 prop_tlv = TLV(FCP_Proprietary_TLV_MAP)
700 prop = prop_tlv.parse(fcp['A5'])
701 fcp['A5'] = tlv_val_interpret(FCP_prorietary_interpreter_map, prop)
702 fcp['A5'] = tlv_key_replace(FCP_Proprietary_TLV_MAP, fcp['A5'])
703 # finally make sure we get human-readable keys in the output dict
704 r = tlv_val_interpret(FCP_interpreter_map, fcp)
705 return tlv_key_replace(FCP_TLV_MAP, r)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100706
707 @staticmethod
708 def match_with_card(scc:SimCardCommands) -> bool:
709 return match_uicc(scc)
710
711class CardProfileUICCSIM(CardProfileUICC):
712 """Same as above, but including 2G SIM support"""
713
714 ORDER = 0
715
716 def __init__(self):
717 super().__init__('UICC-SIM')
718
719 # Add GSM specific files
720 self.files_in_mf.append(DF_TELECOM())
721 self.files_in_mf.append(DF_GSM())
722
723 @staticmethod
724 def match_with_card(scc:SimCardCommands) -> bool:
725 return match_uicc(scc) and match_sim(scc)