blob: a9f2318371b2e80332b6b879b48735a5e2ce522b [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')
381 @cmd2.with_argparser(read_rec_parser)
382 def do_read_record(self, opts):
383 """Read a record from a record-oriented EF"""
384 (data, sw) = self._cmd.rs.read_record(opts.record_nr)
385 self._cmd.poutput(data)
386
387 read_rec_dec_parser = argparse.ArgumentParser()
388 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
389 @cmd2.with_argparser(read_rec_dec_parser)
390 def do_read_record_decoded(self, opts):
391 """Read + decode a record from a record-oriented EF"""
392 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
393 self._cmd.poutput(json.dumps(data, indent=4))
394
395 upd_rec_parser = argparse.ArgumentParser()
396 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
397 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
398 @cmd2.with_argparser(upd_rec_parser)
399 def do_update_record(self, opts):
400 """Update (write) data to a record-oriented EF"""
401 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
402 self._cmd.poutput(data)
403
404 upd_rec_dec_parser = argparse.ArgumentParser()
405 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
406 upd_rec_dec_parser.add_argument('data', help='Data bytes (hex format) to write')
407 @cmd2.with_argparser(upd_rec_dec_parser)
408 def do_update_record_decoded(self, opts):
409 """Encode + Update (write) data to a record-oriented EF"""
410 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, opts.data)
411 self._cmd.poutput(data)
412
413 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
414 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
415 self.rec_len = rec_len
416 self.shell_commands = [self.ShellCommands()]
417
418 def decode_record_hex(self, raw_hex_data):
419 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
420 method = getattr(self, '_decode_record_hex', None)
421 if callable(method):
422 return method(raw_hex_data)
423 raw_bin_data = h2b(raw_hex_data)
424 method = getattr(self, '_decode_record_bin', None)
425 if callable(method):
426 return method(raw_bin_data)
427 return {'raw': raw_bin_data.hex()}
428
429 def decode_record_bin(self, raw_bin_data):
430 """Decode raw (binary) data into abstract representation. Overloaded by specific classes."""
431 method = getattr(self, '_decode_record_bin', None)
432 if callable(method):
433 return method(raw_bin_data)
434 raw_hex_data = b2h(raw_bin_data)
435 method = getattr(self, '_decode_record_hex', None)
436 if callable(method):
437 return method(raw_hex_data)
438 return {'raw': raw_hex_data}
439
440 def encode_record_hex(self, abstract_data):
441 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
442 method = getattr(self, '_encode_record_hex', None)
443 if callable(method):
444 return method(abstract_data)
445 method = getattr(self, '_encode_record_bin', None)
446 if callable(method):
447 raw_bin_data = method(abstract_data)
448 return b2h(raww_bin_data)
449 raise NotImplementedError
450
451 def encode_record_bin(self, abstract_data):
452 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
453 method = getattr(self, '_encode_record_bin', None)
454 if callable(method):
455 return method(abstract_data)
456 method = getattr(self, '_encode_record_hex', None)
457 if callable(method):
458 return b2h(method(abstract_data))
459 raise NotImplementedError
460
461class CyclicEF(LinFixedEF):
462 """Cyclic EF (Entry File) in the smart card filesystem"""
463 # we don't really have any special support for those; just recycling LinFixedEF here
464 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len={1,None}):
465 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
466
467class TransRecEF(TransparentEF):
468 """Transparent EF (Entry File) containing fixed-size records.
469 These are the real odd-balls and mostly look like mistakes in the specification:
470 Specified as 'transparent' EF, but actually containing several fixed-length records
471 inside.
472 We add a special class for those, so the user only has to provide encoder/decoder functions
473 for a record, while this class takes care of split / merge of records.
474 """
475 def __init__(self, fid, sfid=None, name=None, desc=None, parent=None, rec_len=None, size={1,None}):
476 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
477 self.rec_len = rec_len
478
479 def decode_record_hex(self, raw_hex_data):
480 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
481 method = getattr(self, '_decode_record_hex', None)
482 if callable(method):
483 return method(raw_hex_data)
484 method = getattr(self, '_decode_record_bin', None)
485 if callable(method):
486 raw_bin_data = h2b(raw_hex_data)
487 return method(raw_bin_data)
488 return {'raw': raw_hex_data}
489
490 def decode_record_bin(self, raw_bin_data):
491 """Decode raw (hex string) data into abstract representation. Overloaded by specific classes."""
492 method = getattr(self, '_decode_record_bin', None)
493 if callable(method):
494 return method(raw_bin_data)
495 raw_hex_data = b2h(raw_bin_data)
496 method = getattr(self, '_decode_record_hex', None)
497 if callable(method):
498 return method(raw_hex_data)
499 return {'raw': raw_hex_data}
500
501 def encode_record_hex(self, abstract_data):
502 """Encode abstract representation into raw (hex string) data. Overloaded by specific classes."""
503 method = getattr(self, '_encode_record_hex', None)
504 if callable(method):
505 return method(abstract_data)
506 method = getattr(self, '_encode_record_bin', None)
507 if callable(method):
508 return h2b(method(abstract_data))
509 raise NotImplementedError
510
511 def encode_record_bin(self, abstract_data):
512 """Encode abstract representation into raw (binary) data. Overloaded by specific classes."""
513 method = getattr(self, '_encode_record_bin', None)
514 if callable(method):
515 return method(abstract_data)
516 method = getattr(self, '_encode_record_hex', None)
517 if callable(method):
518 return h2b(method(abstract_data))
519 raise NotImplementedError
520
521 def _decode_bin(self, raw_bin_data):
522 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
523 return [self.decode_record_bin(x) for x in chunks]
524
525 def _encode_bin(self, abstract_data):
526 chunks = [self.encode_record_bin(x) for x in abstract_data]
527 # FIXME: pad to file size
528 return b''.join(chunks)
529
530
531
532
533
534class RuntimeState(object):
535 """Represent the runtime state of a session with a card."""
536 def __init__(self, card, profile):
537 self.mf = CardMF()
538 self.card = card
539 self.selected_file = self.mf
540 self.profile = profile
541 # add applications + MF-files from profile
542 for a in self.profile.applications:
543 self.mf.add_application(a)
544 for f in self.profile.files_in_mf:
545 self.mf.add_file(f)
546
547 def get_cwd(self):
548 """Obtain the current working directory."""
549 if isinstance(self.selected_file, CardDF):
550 return self.selected_file
551 else:
552 return self.selected_file.parent
553
554 def get_application(self):
555 """Obtain the currently selected application (if any)."""
556 # iterate upwards from selected file; check if any is an ADF
557 node = self.selected_file
558 while node.parent != node:
559 if isinstance(node, CardADF):
560 return node
561 node = node.parent
562 return None
563
564 def interpret_sw(self, sw):
565 """Interpret the given SW relative to the currently selected Application
566 or the underlying profile."""
567 app = self.get_application()
568 if app:
569 # The application either comes with its own interpret_sw
570 # method or we will use the interpret_sw method from the
571 # card profile.
572 if hasattr(app, "interpret_sw"):
573 return app.interpret_sw(sw)
574 else:
575 return self.profile.interpret_sw(sw)
576 return app.interpret_sw(sw)
577 else:
578 return self.profile.interpret_sw(sw)
579
580 def select(self, name, cmd_app=None):
581 """Change current directory"""
582 sels = self.selected_file.get_selectables()
583 if name in sels:
584 f = sels[name]
585 # unregister commands of old file
586 if cmd_app and self.selected_file.shell_commands:
587 for c in self.selected_file.shell_commands:
588 cmd_app.unregister_command_set(c)
589 try:
590 if isinstance(f, CardADF):
591 (data, sw) = self.card._scc.select_adf(f.aid)
592 else:
593 (data, sw) = self.card._scc.select_file(f.fid)
594 self.selected_file = f
595 except SwMatchError as swm:
596 k = self.interpret_sw(swm.sw_actual)
597 if not k:
598 raise(swm)
599 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
600 # register commands of new file
601 if cmd_app and self.selected_file.shell_commands:
602 for c in self.selected_file.shell_commands:
603 cmd_app.register_command_set(c)
604 return f.decode_select_response(data)
605 #elif looks_like_fid(name):
606 else:
607 raise ValueError("Cannot select unknown %s" % (name))
608
609 def read_binary(self, length=None, offset=0):
610 if not isinstance(self.selected_file, TransparentEF):
611 raise TypeError("Only works with TransparentEF")
612 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
613
614 def read_binary_dec(self):
615 (data, sw) = self.read_binary()
616 dec_data = self.selected_file.decode_hex(data)
617 print("%s: %s -> %s" % (sw, data, dec_data))
618 return (dec_data, sw)
619
620 def update_binary(self, data_hex, offset=0):
621 if not isinstance(self.selected_file, TransparentEF):
622 raise TypeError("Only works with TransparentEF")
623 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset)
624
625 def update_binary_dec(self, data):
626 data_hex = self.selected_file.encode_hex(data)
627 print("%s -> %s" % (data, data_hex))
628 return self.update_binary(data_hex)
629
630 def read_record(self, rec_nr=0):
631 if not isinstance(self.selected_file, LinFixedEF):
632 raise TypeError("Only works with Linear Fixed EF")
633 # returns a string of hex nibbles
634 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
635
636 def read_record_dec(self, rec_nr=0):
637 (data, sw) = self.read_record(rec_nr)
638 return (self.selected_file.decode_record_hex(data), sw)
639
640 def update_record(self, rec_nr, data_hex):
641 if not isinstance(self.selected_file, LinFixedEF):
642 raise TypeError("Only works with Linear Fixed EF")
643 return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex)
644
645 def update_record_dec(self, rec_nr, data):
646 hex_data = self.selected_file.encode_record_hex(data)
647 return self.update_record(self, rec_nr, data_hex)
648
649
650
651class FileData(object):
652 """Represent the runtime, on-card data."""
653 def __init__(self, fdesc):
654 self.desc = fdesc
655 self.fcp = None
656
657
658def interpret_sw(sw_data, sw):
659 """Interpret a given status word within the profile. Returns tuple of
660 two strings"""
661 for class_str, swdict in sw_data.items():
662 # first try direct match
663 if sw in swdict:
664 return (class_str, swdict[sw])
665 # next try wildcard matches
666 for pattern, descr in swdict.items():
667 if sw_match(sw, pattern):
668 return (class_str, descr)
669 return None
670
671class CardApplication(object):
672 """A card application is represented by an ADF (with contained hierarchy) and optionally
673 some SW definitions."""
674 def __init__(self, name, adf=None, sw={}):
675 self.name = name
676 self.adf = adf
677 self.sw = sw
678
679 def __str__(self):
680 return "APP(%s)" % (self.name)
681
682 def interpret_sw(self, sw):
683 """Interpret a given status word within the application. Returns tuple of
684 two strings"""
685 return interpret_sw(self.sw, sw)
686
687class CardProfile(object):
688 """A Card Profile describes a card, it's filessystem hierarchy, an [initial] list of
689 applications as well as profile-specific SW and shell commands. Every card has
690 one card profile, but there may be multiple applications within that profile."""
691 def __init__(self, name, desc=None, files_in_mf=[], sw=[], applications=[], shell_cmdsets=[]):
692 self.name = name
693 self.desc = desc
694 self.files_in_mf = files_in_mf
695 self.sw = sw
696 self.applications = applications
697 self.shell_cmdsets = shell_cmdsets
698
699 def __str__(self):
700 return self.name
701
702 def add_application(self, app):
703 self.applications.add(app)
704
705 def interpret_sw(self, sw):
706 """Interpret a given status word within the profile. Returns tuple of
707 two strings"""
708 return interpret_sw(self.sw, sw)
709
710
711######################################################################
712
713if __name__ == '__main__':
714 mf = CardMF()
715
716 adf_usim = ADF('a0000000871002', name='ADF_USIM')
717 mf.add_application(adf_usim)
718 df_pb = CardDF('5f3a', name='DF.PHONEBOOK')
719 adf_usim.add_file(df_pb)
720 adf_usim.add_file(TransparentEF('6f05', name='EF.LI', size={2,16}))
721 adf_usim.add_file(TransparentEF('6f07', name='EF.IMSI', size={9,9}))
722
723 rss = RuntimeState(mf, None)
724
725 interp = code.InteractiveConsole(locals={'mf':mf, 'rss':rss})
726 interp.interact()