blob: 664c7229aeab9fd79e79796a79dc2a0288228a9b [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001# coding=utf-8
2"""Representation of the ISO7816-4 filesystem model.
3
4The File (and its derived classes) represent the structure / hierarchy
5of the ISO7816-4 smart card file system with the MF, DF, EF and ADF
6entries, further sub-divided into the EF sub-types Transparent, Linear Fixed, etc.
7
8The classes are intended to represent the *specification* of the filesystem,
9not the actual contents / runtime state of interacting with a given smart card.
10
11(C) 2021 by Harald Welte <laforge@osmocom.org>
12
13This program is free software: you can redistribute it and/or modify
14it under the terms of the GNU General Public License as published by
15the Free Software Foundation, either version 2 of the License, or
16(at your option) any later version.
17
18This program is distributed in the hope that it will be useful,
19but WITHOUT ANY WARRANTY; without even the implied warranty of
20MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License
24along with this program. If not, see <http://www.gnu.org/licenses/>.
25"""
26
27import code
28import json
29
30import cmd2
31from cmd2 import CommandSet, with_default_category, with_argparser
32import argparse
33
34from pySim.utils import sw_match, h2b, b2h
35from pySim.exceptions import *
36
37class CardFile(object):
38 """Base class for all objects in the smart card filesystem.
39 Serve as a common ancestor to all other file types; rarely used directly.
40 """
41 RESERVED_NAMES = ['..', '.', '/', 'MF']
42 RESERVED_FIDS = ['3f00']
43
44 def __init__(self, fid=None, sfid=None, name=None, desc=None, parent=None):
45 if not isinstance(self, CardADF) and fid == None:
46 raise ValueError("fid is mandatory")
47 if fid:
48 fid = fid.lower()
49 self.fid = fid # file identifier
50 self.sfid = sfid # short file identifier
51 self.name = name # human readable name
52 self.desc = desc # human readable description
53 self.parent = parent
54 if self.parent and self.parent != self and self.fid:
55 self.parent.add_file(self)
56 self.shell_commands = []
57
58 def __str__(self):
59 if self.name:
60 return self.name
61 else:
62 return self.fid
63
64 def _path_element(self, prefer_name):
65 if prefer_name and self.name:
66 return self.name
67 else:
68 return self.fid
69
70 def fully_qualified_path(self, prefer_name=True):
71 """Return fully qualified path to file as list of FID or name strings."""
72 if self.parent != self:
73 ret = self.parent.fully_qualified_path(prefer_name)
74 else:
75 ret = []
76 ret.append(self._path_element(prefer_name))
77 return ret
78
79 def get_mf(self):
80 """Return the MF (root) of the file system."""
81 if self.parent == None:
82 return None
83 # iterate towards the top. MF has parent == self
84 node = self
85 while node.parent != node:
86 node = node.parent
87 return node
88
Philipp Maier786f7812021-02-25 16:48:10 +010089 def _get_self_selectables(self, alias=None, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +010090 """Return a dict of {'identifier': self} tuples"""
91 sels = {}
92 if alias:
93 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +010094 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +010095 sels.update({self.fid: self})
Philipp Maier786f7812021-02-25 16:48:10 +010096 if self.name and (flags == [] or 'NAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +010097 sels.update({self.name: self})
98 return sels
99
Philipp Maier786f7812021-02-25 16:48:10 +0100100 def get_selectables(self, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +0100101 """Return a dict of {'identifier': File} that is selectable from the current file."""
Philipp Maier786f7812021-02-25 16:48:10 +0100102 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100103 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100104 if flags == [] or 'SELF' in flags:
105 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100106 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100107 if flags == [] or 'PARENT' in flags:
108 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100109 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100110 if flags == [] or 'MF' in flags:
111 mf = self.get_mf()
112 if mf:
113 sels.update(mf._get_self_selectables(flags = flags))
114 if flags == [] or 'APPS' in flags:
115 sels.update(mf.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100116 return sels
117
Philipp Maier786f7812021-02-25 16:48:10 +0100118 def get_selectable_names(self, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +0100119 """Return a list of strings for all identifiers that are selectable from the current file."""
Philipp Maier786f7812021-02-25 16:48:10 +0100120 sels = self.get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100121 return sels.keys()
122
123 def decode_select_response(self, data_hex):
124 """Decode the response to a SELECT command."""
125 return self.parent.decode_select_response(data_hex)
126
127
128class CardDF(CardFile):
129 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
130 def __init__(self, **kwargs):
131 if not isinstance(self, CardADF):
132 if not 'fid' in kwargs:
133 raise TypeError('fid is mandatory for all DF')
134 super().__init__(**kwargs)
135 self.children = dict()
136
137 def __str__(self):
138 return "DF(%s)" % (super().__str__())
139
140 def add_file(self, child, ignore_existing=False):
141 """Add a child (DF/EF) to this DF"""
142 if not isinstance(child, CardFile):
143 raise TypeError("Expected a File instance")
144 if child.name in CardFile.RESERVED_NAMES:
145 raise ValueError("File name %s is a reserved name" % (child.name))
146 if child.fid in CardFile.RESERVED_FIDS:
147 raise ValueError("File fid %s is a reserved name" % (child.fid))
148 if child.fid in self.children:
149 if ignore_existing:
150 return
151 raise ValueError("File with given fid %s already exists" % (child.fid))
152 if self.lookup_file_by_sfid(child.sfid):
153 raise ValueError("File with given sfid %s already exists" % (child.sfid))
154 if self.lookup_file_by_name(child.name):
155 if ignore_existing:
156 return
157 raise ValueError("File with given name %s already exists" % (child.name))
158 self.children[child.fid] = child
159 child.parent = self
160
161 def add_files(self, children, ignore_existing=False):
162 """Add a list of child (DF/EF) to this DF"""
163 for child in children:
164 self.add_file(child, ignore_existing)
165
Philipp Maier786f7812021-02-25 16:48:10 +0100166 def get_selectables(self, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +0100167 """Get selectable (DF/EF names) from current DF"""
168 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100169 sels = super().get_selectables(flags)
170 if flags == [] or 'FIDS' in flags:
171 sels.update({x.fid: x for x in self.children.values() if x.fid})
172 if flags == [] or 'NAMES' in flags:
173 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100174 return sels
175
176 def lookup_file_by_name(self, name):
177 if name == None:
178 return None
179 for i in self.children.values():
180 if i.name and i.name == name:
181 return i
182 return None
183
184 def lookup_file_by_sfid(self, sfid):
185 if sfid == None:
186 return None
187 for i in self.children.values():
188 if i.sfid == int(sfid):
189 return i
190 return None
191
192 def lookup_file_by_fid(self, fid):
193 if fid in self.children:
194 return self.children[fid]
195 return None
196
197
198class CardMF(CardDF):
199 """MF (Master File) in the smart card filesystem"""
200 def __init__(self, **kwargs):
201 # can be overridden; use setdefault
202 kwargs.setdefault('fid', '3f00')
203 kwargs.setdefault('name', 'MF')
204 kwargs.setdefault('desc', 'Master File (directory root)')
205 # cannot be overridden; use assignment
206 kwargs['parent'] = self
207 super().__init__(**kwargs)
208 self.applications = dict()
209
210 def __str__(self):
211 return "MF(%s)" % (self.fid)
212
213 def add_application(self, app):
214 """Add an ADF (Application Dedicated File) to the MF"""
215 if not isinstance(app, CardADF):
216 raise TypeError("Expected an ADF instance")
217 if app.aid in self.applications:
218 raise ValueError("AID %s already exists" % (app.aid))
219 self.applications[app.aid] = app
220 app.parent=self
221
222 def get_app_names(self):
223 """Get list of completions (AID names)"""
224 return [x.name for x in self.applications]
225
Philipp Maier786f7812021-02-25 16:48:10 +0100226 def get_selectables(self, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +0100227 """Get list of completions (DF/EF/ADF names) from current DF"""
Philipp Maier786f7812021-02-25 16:48:10 +0100228 sels = super().get_selectables(flags)
229 if flags == [] or 'APPS' in flags:
230 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100231 return sels
232
Philipp Maier786f7812021-02-25 16:48:10 +0100233 def get_app_selectables(self, flags = []):
234 """Get applications by AID + name"""
235 sels = {}
236 if flags == [] or 'FIDS' in flags:
237 sels.update({x.aid: x for x in self.applications.values()})
238 if flags == [] or 'NAMES' in flags:
239 sels.update({x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100240 return sels
241
242 def decode_select_response(self, data_hex):
243 """Decode the response to a SELECT command."""
244 return data_hex
245
246
247
248class CardADF(CardDF):
249 """ADF (Application Dedicated File) in the smart card filesystem"""
250 def __init__(self, aid, **kwargs):
251 super().__init__(**kwargs)
252 self.aid = aid # Application Identifier
253 if self.parent:
254 self.parent.add_application(self)
255
256 def __str__(self):
257 return "ADF(%s)" % (self.aid)
258
259 def _path_element(self, prefer_name):
260 if self.name and prefer_name:
261 return self.name
262 else:
263 return self.aid
264
265
266class CardEF(CardFile):
267 """EF (Entry File) in the smart card filesystem"""
268 def __init__(self, *, fid, **kwargs):
269 kwargs['fid'] = fid
270 super().__init__(**kwargs)
271
272 def __str__(self):
273 return "EF(%s)" % (super().__str__())
274
Philipp Maier786f7812021-02-25 16:48:10 +0100275 def get_selectables(self, flags = []):
Harald Welteb2edd142021-01-08 23:29:35 +0100276 """Get list of completions (EF names) from current DF"""
277 #global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100278 sels = super().get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100279 sels.update({x.name:x for x in self.parent.children.values() if x != self})
280 return sels
281
282
283class TransparentEF(CardEF):
284 """Transparent EF (Entry File) in the smart card filesystem"""
285
286 @with_default_category('Transparent EF Commands')
287 class ShellCommands(CommandSet):
288 def __init__(self):
289 super().__init__()
290
291 read_bin_parser = argparse.ArgumentParser()
292 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
293 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
294 @cmd2.with_argparser(read_bin_parser)
295 def do_read_binary(self, opts):
296 """Read binary data from a transparent EF"""
297 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
298 self._cmd.poutput(data)
299
300 def do_read_binary_decoded(self, opts):
301 """Read + decode data from a transparent EF"""
302 (data, sw) = self._cmd.rs.read_binary_dec()
303 self._cmd.poutput(json.dumps(data, indent=4))
304
305 upd_bin_parser = argparse.ArgumentParser()
306 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
307 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
308 @cmd2.with_argparser(upd_bin_parser)
309 def do_update_binary(self, opts):
310 """Update (Write) data of a transparent EF"""
311 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
312 self._cmd.poutput(data)
313
314 upd_bin_dec_parser = argparse.ArgumentParser()
315 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
316 @cmd2.with_argparser(upd_bin_dec_parser)
317 def do_update_binary_decoded(self, opts):
318 """Encode + Update (Write) data of a transparent EF"""
319 data_json = json.loads(opts.data)
320 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
321 self._cmd.poutput(json.dumps(data, indent=4))
322
323 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, size={1,None}):
324 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
325 self.size = size
326 self.shell_commands = [self.ShellCommands()]
327
328 def decode_bin(self, raw_bin_data):
329 """Decode raw (binary) data into abstract representation. Overloaded by specific classes."""
330 method = getattr(self, '_decode_bin', None)
331 if callable(method):
332 return method(raw_bin_data)
333 method = getattr(self, '_decode_hex', None)
334 if callable(method):
335 return method(b2h(raw_bin_data))
336 return {'raw': raw_bin_data.hex()}
337
338 def decode_hex(self, raw_hex_data):
339 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
340 method = getattr(self, '_decode_hex', None)
341 if callable(method):
342 return method(raw_hex_data)
343 raw_bin_data = h2b(raw_hex_data)
344 method = getattr(self, '_decode_bin', None)
345 if callable(method):
346 return method(raw_bin_data)
347 return {'raw': raw_bin_data.hex()}
348
349 def encode_bin(self, abstract_data):
350 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
351 method = getattr(self, '_encode_bin', None)
352 if callable(method):
353 return method(abstract_data)
354 method = getattr(self, '_encode_hex', None)
355 if callable(method):
356 return h2b(method(abstract_data))
357 raise NotImplementedError
358
359 def encode_hex(self, abstract_data):
360 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
361 method = getattr(self, '_encode_hex', None)
362 if callable(method):
363 return method(abstract_data)
364 method = getattr(self, '_encode_bin', None)
365 if callable(method):
366 raw_bin_data = method(abstract_data)
367 return b2h(raw_bin_data)
368 raise NotImplementedError
369
370
371class LinFixedEF(CardEF):
372 """Linear Fixed EF (Entry File) in the smart card filesystem"""
373
374 @with_default_category('Linear Fixed EF Commands')
375 class ShellCommands(CommandSet):
376 def __init__(self):
377 super().__init__()
378
379 read_rec_parser = argparse.ArgumentParser()
380 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100381 read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
Harald Welteb2edd142021-01-08 23:29:35 +0100382 @cmd2.with_argparser(read_rec_parser)
383 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100384 """Read one or multiple records from a record-oriented EF"""
385 for r in range(opts.count):
386 recnr = opts.record_nr + r
387 (data, sw) = self._cmd.rs.read_record(recnr)
388 if (len(data) > 0):
389 recstr = str(data)
390 else:
391 recstr = "(empty)"
392 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100393
394 read_rec_dec_parser = argparse.ArgumentParser()
395 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
396 @cmd2.with_argparser(read_rec_dec_parser)
397 def do_read_record_decoded(self, opts):
398 """Read + decode a record from a record-oriented EF"""
399 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
400 self._cmd.poutput(json.dumps(data, indent=4))
401
402 upd_rec_parser = argparse.ArgumentParser()
403 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
404 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
405 @cmd2.with_argparser(upd_rec_parser)
406 def do_update_record(self, opts):
407 """Update (write) data to a record-oriented EF"""
408 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
409 self._cmd.poutput(data)
410
411 upd_rec_dec_parser = argparse.ArgumentParser()
412 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
413 upd_rec_dec_parser.add_argument('data', help='Data bytes (hex format) to write')
414 @cmd2.with_argparser(upd_rec_dec_parser)
415 def do_update_record_decoded(self, opts):
416 """Encode + Update (write) data to a record-oriented EF"""
417 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, opts.data)
418 self._cmd.poutput(data)
419
420 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
421 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
422 self.rec_len = rec_len
423 self.shell_commands = [self.ShellCommands()]
424
425 def decode_record_hex(self, raw_hex_data):
426 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
427 method = getattr(self, '_decode_record_hex', None)
428 if callable(method):
429 return method(raw_hex_data)
430 raw_bin_data = h2b(raw_hex_data)
431 method = getattr(self, '_decode_record_bin', None)
432 if callable(method):
433 return method(raw_bin_data)
434 return {'raw': raw_bin_data.hex()}
435
436 def decode_record_bin(self, raw_bin_data):
437 """Decode raw (binary) data into abstract representation. Overloaded by specific classes."""
438 method = getattr(self, '_decode_record_bin', None)
439 if callable(method):
440 return method(raw_bin_data)
441 raw_hex_data = b2h(raw_bin_data)
442 method = getattr(self, '_decode_record_hex', None)
443 if callable(method):
444 return method(raw_hex_data)
445 return {'raw': raw_hex_data}
446
447 def encode_record_hex(self, abstract_data):
448 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
449 method = getattr(self, '_encode_record_hex', None)
450 if callable(method):
451 return method(abstract_data)
452 method = getattr(self, '_encode_record_bin', None)
453 if callable(method):
454 raw_bin_data = method(abstract_data)
455 return b2h(raww_bin_data)
456 raise NotImplementedError
457
458 def encode_record_bin(self, abstract_data):
459 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
460 method = getattr(self, '_encode_record_bin', None)
461 if callable(method):
462 return method(abstract_data)
463 method = getattr(self, '_encode_record_hex', None)
464 if callable(method):
465 return b2h(method(abstract_data))
466 raise NotImplementedError
467
468class CyclicEF(LinFixedEF):
469 """Cyclic EF (Entry File) in the smart card filesystem"""
470 # we don't really have any special support for those; just recycling LinFixedEF here
471 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
472 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
473
474class TransRecEF(TransparentEF):
475 """Transparent EF (Entry File) containing fixed-size records.
476 These are the real odd-balls and mostly look like mistakes in the specification:
477 Specified as 'transparent' EF, but actually containing several fixed-length records
478 inside.
479 We add a special class for those, so the user only has to provide encoder/decoder functions
480 for a record, while this class takes care of split / merge of records.
481 """
482 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len=None, size={1,None}):
483 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
484 self.rec_len = rec_len
485
486 def decode_record_hex(self, raw_hex_data):
487 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
488 method = getattr(self, '_decode_record_hex', None)
489 if callable(method):
490 return method(raw_hex_data)
491 method = getattr(self, '_decode_record_bin', None)
492 if callable(method):
493 raw_bin_data = h2b(raw_hex_data)
494 return method(raw_bin_data)
495 return {'raw': raw_hex_data}
496
497 def decode_record_bin(self, raw_bin_data):
498 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
499 method = getattr(self, '_decode_record_bin', None)
500 if callable(method):
501 return method(raw_bin_data)
502 raw_hex_data = b2h(raw_bin_data)
503 method = getattr(self, '_decode_record_hex', None)
504 if callable(method):
505 return method(raw_hex_data)
506 return {'raw': raw_hex_data}
507
508 def encode_record_hex(self, abstract_data):
509 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
510 method = getattr(self, '_encode_record_hex', None)
511 if callable(method):
512 return method(abstract_data)
513 method = getattr(self, '_encode_record_bin', None)
514 if callable(method):
515 return h2b(method(abstract_data))
516 raise NotImplementedError
517
518 def encode_record_bin(self, abstract_data):
519 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
520 method = getattr(self, '_encode_record_bin', None)
521 if callable(method):
522 return method(abstract_data)
523 method = getattr(self, '_encode_record_hex', None)
524 if callable(method):
525 return h2b(method(abstract_data))
526 raise NotImplementedError
527
528 def _decode_bin(self, raw_bin_data):
529 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
530 return [self.decode_record_bin(x) for x in chunks]
531
532 def _encode_bin(self, abstract_data):
533 chunks = [self.encode_record_bin(x) for x in abstract_data]
534 # FIXME: pad to file size
535 return b''.join(chunks)
536
537
538
539
540
541class RuntimeState(object):
542 """Represent the runtime state of a session with a card."""
543 def __init__(self, card, profile):
544 self.mf = CardMF()
545 self.card = card
546 self.selected_file = self.mf
547 self.profile = profile
548 # add applications + MF-files from profile
549 for a in self.profile.applications:
550 self.mf.add_application(a)
551 for f in self.profile.files_in_mf:
552 self.mf.add_file(f)
553
554 def get_cwd(self):
555 """Obtain the current working directory."""
556 if isinstance(self.selected_file, CardDF):
557 return self.selected_file
558 else:
559 return self.selected_file.parent
560
561 def get_application(self):
562 """Obtain the currently selected application (if any)."""
563 # iterate upwards from selected file; check if any is an ADF
564 node = self.selected_file
565 while node.parent != node:
566 if isinstance(node, CardADF):
567 return node
568 node = node.parent
569 return None
570
571 def interpret_sw(self, sw):
572 """Interpret the given SW relative to the currently selected Application
573 or the underlying profile."""
574 app = self.get_application()
575 if app:
576 # The application either comes with its own interpret_sw
577 # method or we will use the interpret_sw method from the
578 # card profile.
579 if hasattr(app, "interpret_sw"):
580 return app.interpret_sw(sw)
581 else:
582 return self.profile.interpret_sw(sw)
583 return app.interpret_sw(sw)
584 else:
585 return self.profile.interpret_sw(sw)
586
587 def select(self, name, cmd_app=None):
588 """Change current directory"""
589 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +0100590 if is_hex(name):
591 name = name.lower()
Harald Welteb2edd142021-01-08 23:29:35 +0100592 if name in sels:
593 f = sels[name]
594 # unregister commands of old file
595 if cmd_app and self.selected_file.shell_commands:
596 for c in self.selected_file.shell_commands:
597 cmd_app.unregister_command_set(c)
598 try:
599 if isinstance(f, CardADF):
600 (data, sw) = self.card._scc.select_adf(f.aid)
601 else:
602 (data, sw) = self.card._scc.select_file(f.fid)
603 self.selected_file = f
604 except SwMatchError as swm:
605 k = self.interpret_sw(swm.sw_actual)
606 if not k:
607 raise(swm)
608 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
609 # register commands of new file
610 if cmd_app and self.selected_file.shell_commands:
611 for c in self.selected_file.shell_commands:
612 cmd_app.register_command_set(c)
613 return f.decode_select_response(data)
614 #elif looks_like_fid(name):
615 else:
616 raise ValueError("Cannot select unknown %s" % (name))
617
618 def read_binary(self, length=None, offset=0):
619 if not isinstance(self.selected_file, TransparentEF):
620 raise TypeError("Only works with TransparentEF")
621 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
622
623 def read_binary_dec(self):
624 (data, sw) = self.read_binary()
625 dec_data = self.selected_file.decode_hex(data)
626 print("%s: %s -> %s" % (sw, data, dec_data))
627 return (dec_data, sw)
628
629 def update_binary(self, data_hex, offset=0):
630 if not isinstance(self.selected_file, TransparentEF):
631 raise TypeError("Only works with TransparentEF")
632 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset)
633
634 def update_binary_dec(self, data):
635 data_hex = self.selected_file.encode_hex(data)
636 print("%s -> %s" % (data, data_hex))
637 return self.update_binary(data_hex)
638
639 def read_record(self, rec_nr=0):
640 if not isinstance(self.selected_file, LinFixedEF):
641 raise TypeError("Only works with Linear Fixed EF")
642 # returns a string of hex nibbles
643 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
644
645 def read_record_dec(self, rec_nr=0):
646 (data, sw) = self.read_record(rec_nr)
647 return (self.selected_file.decode_record_hex(data), sw)
648
649 def update_record(self, rec_nr, data_hex):
650 if not isinstance(self.selected_file, LinFixedEF):
651 raise TypeError("Only works with Linear Fixed EF")
652 return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex)
653
654 def update_record_dec(self, rec_nr, data):
655 hex_data = self.selected_file.encode_record_hex(data)
656 return self.update_record(self, rec_nr, data_hex)
657
658
659
660class FileData(object):
661 """Represent the runtime, on-card data."""
662 def __init__(self, fdesc):
663 self.desc = fdesc
664 self.fcp = None
665
666
667def interpret_sw(sw_data, sw):
668 """Interpret a given status word within the profile. Returns tuple of
669 two strings"""
670 for class_str, swdict in sw_data.items():
671 # first try direct match
672 if sw in swdict:
673 return (class_str, swdict[sw])
674 # next try wildcard matches
675 for pattern, descr in swdict.items():
676 if sw_match(sw, pattern):
677 return (class_str, descr)
678 return None
679
680class CardApplication(object):
681 """A card application is represented by an ADF (with contained hierarchy) and optionally
682 some SW definitions."""
683 def __init__(self, name, adf=None, sw={}):
684 self.name = name
685 self.adf = adf
686 self.sw = sw
687
688 def __str__(self):
689 return "APP(%s)" % (self.name)
690
691 def interpret_sw(self, sw):
692 """Interpret a given status word within the application. Returns tuple of
693 two strings"""
694 return interpret_sw(self.sw, sw)
695
696class CardProfile(object):
697 """A Card Profile describes a card, it's filessystem hierarchy, an [initial] list of
698 applications as well as profile-specific SW and shell commands. Every card has
699 one card profile, but there may be multiple applications within that profile."""
700 def __init__(self, name, desc=None, files_in_mf=[], sw=[], applications=[], shell_cmdsets=[]):
701 self.name = name
702 self.desc = desc
703 self.files_in_mf = files_in_mf
704 self.sw = sw
705 self.applications = applications
706 self.shell_cmdsets = shell_cmdsets
707
708 def __str__(self):
709 return self.name
710
711 def add_application(self, app):
712 self.applications.add(app)
713
714 def interpret_sw(self, sw):
715 """Interpret a given status word within the profile. Returns tuple of
716 two strings"""
717 return interpret_sw(self.sw, sw)
718
719
720######################################################################
721
722if __name__ == '__main__':
723 mf = CardMF()
724
725 adf_usim = ADF('a0000000871002', name='ADF_USIM')
726 mf.add_application(adf_usim)
727 df_pb = CardDF('5f3a', name='DF.PHONEBOOK')
728 adf_usim.add_file(df_pb)
729 adf_usim.add_file(TransparentEF('6f05', name='EF.LI', size={2,16}))
730 adf_usim.add_file(TransparentEF('6f07', name='EF.IMSI', size={9,9}))
731
732 rss = RuntimeState(mf, None)
733
734 interp = code.InteractiveConsole(locals={'mf':mf, 'rss':rss})
735 interp.interact()