blob: 1d109883a8aedcf86068c740e547a2afc5586d66 [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})
506
507# TS 102 221 Section 13.4
508class EF_ARR(LinFixedEF):
509 def __init__(self, fid='2f06', sfid=0x06, name='EF.ARR', desc='Access Rule Reference'):
510 super().__init__(fid, sfid=sfid, name=name, desc=desc)
Harald Welte4ae228a2021-05-02 21:29:04 +0200511 # add those commands to the general commands of a TransparentEF
512 self.shell_commands += [self.AddlShellCommands()]
513
514 @staticmethod
515 def flatten(inp:list):
516 """Flatten the somewhat deep/complex/nested data returned from decoder."""
517 def sc_abbreviate(sc):
518 if 'always' in sc:
519 return 'always'
520 elif 'never' in sc:
521 return 'never'
522 elif 'control_reference_template' in sc:
523 return sc['control_reference_template']
524 else:
525 return sc
526
527 by_mode = {}
528 for t in inp:
529 am = t[0]
530 sc = t[1]
531 sc_abbr = sc_abbreviate(sc)
532 if 'access_mode' in am:
533 for m in am['access_mode']:
534 by_mode[m] = sc_abbr
535 elif 'command_header' in am:
536 ins = am['command_header']['INS']
537 if 'CLA' in am['command_header']:
538 cla = am['command_header']['CLA']
539 else:
540 cla = None
541 cmd = ts_102_22x_cmdset.lookup(ins, cla)
542 if cmd:
543 name = cmd.name.lower().replace(' ','_')
544 by_mode[name] = sc_abbr
545 else:
546 raise ValueError
547 else:
548 raise ValueError
549 return by_mode
550
551 def _decode_record_bin(self, raw_bin_data):
552 # we can only guess if we should decode for EF or DF here :(
553 arr_seq = DataObjectSequence('arr', sequence = [AM_DO_EF, SC_DO])
554 dec = arr_seq.decode_multi(raw_bin_data)
555 # we cannot pass the result through flatten() here, as we don't have a related
556 # 'un-flattening' decoder, and hence would be unable to encode :(
557 return dec[0]
558
559 @with_default_category('File-Specific Commands')
560 class AddlShellCommands(CommandSet):
561 def __init__(self):
562 super().__init__()
563
564 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_rec_dec_parser)
565 def do_read_arr_record(self, opts):
566 """Read one EF.ARR record in flattened, human-friendly form."""
567 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
568 data = self._cmd.rs.selected_file.flatten(data)
569 self._cmd.poutput_json(data, opts.oneline)
570
571 @cmd2.with_argparser(LinFixedEF.ShellCommands.read_recs_dec_parser)
572 def do_read_arr_records(self, opts):
573 """Read + decode all EF.ARR records in flattened, human-friendly form."""
574 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
575 # collect all results in list so they are rendered as JSON list when printing
576 data_list = []
577 for recnr in range(1, 1 + num_of_rec):
578 (data, sw) = self._cmd.rs.read_record_dec(recnr)
579 data = self._cmd.rs.selected_file.flatten(data)
580 data_list.append(data)
581 self._cmd.poutput_json(data_list, opts.oneline)
582
Harald Welteb2edd142021-01-08 23:29:35 +0100583
584# TS 102 221 Section 13.6
585class EF_UMPC(TransparentEF):
586 def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
587 super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5})
Harald Welte8f892fb2021-06-05 10:12:43 +0200588 addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
589 self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
Harald Welteb2edd142021-01-08 23:29:35 +0100590
Harald Welteb2edd142021-01-08 23:29:35 +0100591class CardProfileUICC(CardProfile):
Philipp Maiera028c7d2021-11-08 16:12:03 +0100592
593 ORDER = 1
594
595 def __init__(self, name = 'UICC'):
Harald Welteb2edd142021-01-08 23:29:35 +0100596 files = [
597 EF_DIR(),
598 EF_ICCID(),
599 EF_PL(),
600 EF_ARR(),
601 # FIXME: DF.CD
602 EF_UMPC(),
603 ]
604 sw = {
605 'Normal': {
606 '9000': 'Normal ending of the command',
607 '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal',
608 '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session',
609 },
610 'Postponed processing': {
611 '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed',
612 },
613 'Warnings': {
614 '6200': 'No information given, state of non-volatile memory unchanged',
615 '6281': 'Part of returned data may be corrupted',
616 '6282': 'End of file/record reached before reading Le bytes or unsuccessful search',
617 '6283': 'Selected file invalidated',
618 '6284': 'Selected file in termination state',
619 '62f1': 'More data available',
620 '62f2': 'More data available and proactive command pending',
621 '62f3': 'Response data available',
622 '63f1': 'More data expected',
623 '63f2': 'More data expected and proactive command pending',
624 '63cx': 'Command successful but after using an internal update retry routine X times',
625 },
626 'Execution errors': {
627 '6400': 'No information given, state of non-volatile memory unchanged',
628 '6500': 'No information given, state of non-volatile memory changed',
629 '6581': 'Memory problem',
630 },
631 'Checking errors': {
632 '6700': 'Wrong length',
633 '67xx': 'The interpretation of this status word is command dependent',
634 '6b00': 'Wrong parameter(s) P1-P2',
635 '6d00': 'Instruction code not supported or invalid',
636 '6e00': 'Class not supported',
637 '6f00': 'Technical problem, no precise diagnosis',
638 '6fxx': 'The interpretation of this status word is command dependent',
639 },
640 'Functions in CLA not supported': {
641 '6800': 'No information given',
642 '6881': 'Logical channel not supported',
643 '6882': 'Secure messaging not supported',
644 },
645 'Command not allowed': {
646 '6900': 'No information given',
647 '6981': 'Command incompatible with file structure',
648 '6982': 'Security status not satisfied',
649 '6983': 'Authentication/PIN method blocked',
650 '6984': 'Referenced data invalidated',
651 '6985': 'Conditions of use not satisfied',
652 '6986': 'Command not allowed (no EF selected)',
653 '6989': 'Command not allowed - secure channel - security not satisfied',
654 },
655 'Wrong parameters': {
656 '6a80': 'Incorrect parameters in the data field',
657 '6a81': 'Function not supported',
658 '6a82': 'File not found',
659 '6a83': 'Record not found',
660 '6a84': 'Not enough memory space',
661 '6a86': 'Incorrect parameters P1 to P2',
662 '6a87': 'Lc inconsistent with P1 to P2',
663 '6a88': 'Referenced data not found',
664 },
665 'Application errors': {
666 '9850': 'INCREASE cannot be performed, max value reached',
667 '9862': 'Authentication error, application specific',
668 '9863': 'Security session or association expired',
669 '9864': 'Minimum UICC suspension time is too long',
670 },
671 }
672
Philipp Maiera028c7d2021-11-08 16:12:03 +0100673 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 +0100674
Philipp Maier5998a3a2021-11-16 15:16:39 +0100675 @staticmethod
676 def decode_select_response(resp_hex:str) -> Any:
677 """ETSI TS 102 221 Section 11.1.1.3"""
678 fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
679 resp_hex = resp_hex.upper()
680 # outer layer
681 fcp_base_tlv = TLV(['62'])
682 fcp_base = fcp_base_tlv.parse(resp_hex)
683 # actual FCP
684 fcp_tlv = TLV(FCP_TLV_MAP)
685 fcp = fcp_tlv.parse(fcp_base['62'])
686 # further decode the proprietary information
687 if fcp['A5']:
688 prop_tlv = TLV(FCP_Proprietary_TLV_MAP)
689 prop = prop_tlv.parse(fcp['A5'])
690 fcp['A5'] = tlv_val_interpret(FCP_prorietary_interpreter_map, prop)
691 fcp['A5'] = tlv_key_replace(FCP_Proprietary_TLV_MAP, fcp['A5'])
692 # finally make sure we get human-readable keys in the output dict
693 r = tlv_val_interpret(FCP_interpreter_map, fcp)
694 return tlv_key_replace(FCP_TLV_MAP, r)
Philipp Maiera028c7d2021-11-08 16:12:03 +0100695
696 @staticmethod
697 def match_with_card(scc:SimCardCommands) -> bool:
698 return match_uicc(scc)
699
700class CardProfileUICCSIM(CardProfileUICC):
701 """Same as above, but including 2G SIM support"""
702
703 ORDER = 0
704
705 def __init__(self):
706 super().__init__('UICC-SIM')
707
708 # Add GSM specific files
709 self.files_in_mf.append(DF_TELECOM())
710 self.files_in_mf.append(DF_GSM())
711
712 @staticmethod
713 def match_with_card(scc:SimCardCommands) -> bool:
714 return match_uicc(scc) and match_sim(scc)