blob: f8443f352221a29a24227f147d1ca5ca6f9d7aef [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.
Harald Welteb2edd142021-01-08 23:29:35 +010010"""
11
Harald Welte5a4fd522021-04-02 16:05:26 +020012# (C) 2021 by Harald Welte <laforge@osmocom.org>
13#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26
Harald Welteb2edd142021-01-08 23:29:35 +010027import code
28import json
29
30import cmd2
31from cmd2 import CommandSet, with_default_category, with_argparser
32import argparse
33
Harald Welte1e456572021-04-02 17:16:30 +020034from typing import cast, Optional, Iterable, List, Any, Dict, Tuple
Harald Welteee3501f2021-04-02 13:00:18 +020035
Philipp Maier3aec8712021-03-09 21:49:01 +010036from pySim.utils import sw_match, h2b, b2h, is_hex
Harald Welteb2edd142021-01-08 23:29:35 +010037from pySim.exceptions import *
Harald Welte0d4e98a2021-04-07 00:14:40 +020038from pySim.jsonpath import js_path_find, js_path_modify
Harald Welteb2edd142021-01-08 23:29:35 +010039
40class CardFile(object):
41 """Base class for all objects in the smart card filesystem.
42 Serve as a common ancestor to all other file types; rarely used directly.
43 """
44 RESERVED_NAMES = ['..', '.', '/', 'MF']
45 RESERVED_FIDS = ['3f00']
46
Harald Welteee3501f2021-04-02 13:00:18 +020047 def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,
48 parent:Optional['CardDF']=None):
49 """
50 Args:
51 fid : File Identifier (4 hex digits)
52 sfid : Short File Identifier (2 hex digits, optional)
53 name : Brief name of the file, lik EF_ICCID
54 desc : Descriptoin of the file
55 parent : Parent CardFile object within filesystem hierarchy
56 """
Harald Welteb2edd142021-01-08 23:29:35 +010057 if not isinstance(self, CardADF) and fid == None:
58 raise ValueError("fid is mandatory")
59 if fid:
60 fid = fid.lower()
61 self.fid = fid # file identifier
62 self.sfid = sfid # short file identifier
63 self.name = name # human readable name
64 self.desc = desc # human readable description
65 self.parent = parent
66 if self.parent and self.parent != self and self.fid:
67 self.parent.add_file(self)
Harald Welteee3501f2021-04-02 13:00:18 +020068 self.shell_commands: List[CommandSet] = []
Harald Welteb2edd142021-01-08 23:29:35 +010069
Philipp Maier66061582021-03-09 21:57:57 +010070 # Note: the basic properties (fid, name, ect.) are verified when
71 # the file is attached to a parent file. See method add_file() in
72 # class Card DF
73
Harald Welteb2edd142021-01-08 23:29:35 +010074 def __str__(self):
75 if self.name:
76 return self.name
77 else:
78 return self.fid
79
Harald Welteee3501f2021-04-02 13:00:18 +020080 def _path_element(self, prefer_name:bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +010081 if prefer_name and self.name:
82 return self.name
83 else:
84 return self.fid
85
Harald Welte1e456572021-04-02 17:16:30 +020086 def fully_qualified_path(self, prefer_name:bool=True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +020087 """Return fully qualified path to file as list of FID or name strings.
88
89 Args:
90 prefer_name : Preferably build path of names; fall-back to FIDs as required
91 """
Harald Welte1e456572021-04-02 17:16:30 +020092 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +010093 ret = self.parent.fully_qualified_path(prefer_name)
94 else:
95 ret = []
Harald Welte1e456572021-04-02 17:16:30 +020096 elem = self._path_element(prefer_name)
97 if elem:
98 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +010099 return ret
100
Harald Welteee3501f2021-04-02 13:00:18 +0200101 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100102 """Return the MF (root) of the file system."""
103 if self.parent == None:
104 return None
105 # iterate towards the top. MF has parent == self
106 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200107 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100108 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200109 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100110
Harald Welte1e456572021-04-02 17:16:30 +0200111 def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200112 """Return a dict of {'identifier': self} tuples.
113
114 Args:
115 alias : Add an alias with given name to 'self'
116 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
117 If not specified, all selectables will be returned.
118 Returns:
119 dict containing reference to 'self' for all identifiers.
120 """
Harald Welteb2edd142021-01-08 23:29:35 +0100121 sels = {}
122 if alias:
123 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100124 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100125 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100126 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100127 sels.update({self.name: self})
128 return sels
129
Harald Welte1e456572021-04-02 17:16:30 +0200130 def get_selectables(self, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200131 """Return a dict of {'identifier': File} that is selectable from the current file.
132
133 Args:
134 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
135 If not specified, all selectables will be returned.
136 Returns:
137 dict containing all selectable items. Key is identifier (string), value
138 a reference to a CardFile (or derived class) instance.
139 """
Philipp Maier786f7812021-02-25 16:48:10 +0100140 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100141 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100142 if flags == [] or 'SELF' in flags:
143 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100144 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100145 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200146 if self.parent:
147 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100148 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100149 if flags == [] or 'MF' in flags:
150 mf = self.get_mf()
151 if mf:
152 sels.update(mf._get_self_selectables(flags = flags))
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100153 sels.update(mf.get_app_selectables(flags = flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100154 return sels
155
Harald Welte1e456572021-04-02 17:16:30 +0200156 def get_selectable_names(self, flags = []) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200157 """Return a dict of {'identifier': File} that is selectable from the current file.
158
159 Args:
160 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
161 If not specified, all selectables will be returned.
162 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200163 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200164 """
Philipp Maier786f7812021-02-25 16:48:10 +0100165 sels = self.get_selectables(flags)
Harald Welte1e456572021-04-02 17:16:30 +0200166 return list(sels.keys())
Harald Welteb2edd142021-01-08 23:29:35 +0100167
Harald Welteee3501f2021-04-02 13:00:18 +0200168 def decode_select_response(self, data_hex:str):
Harald Welteb2edd142021-01-08 23:29:35 +0100169 """Decode the response to a SELECT command."""
Harald Welte1e456572021-04-02 17:16:30 +0200170 if self.parent:
171 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100172
173
174class CardDF(CardFile):
175 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100176
177 @with_default_category('DF/ADF Commands')
178 class ShellCommands(CommandSet):
179 def __init__(self):
180 super().__init__()
181
Harald Welteb2edd142021-01-08 23:29:35 +0100182 def __init__(self, **kwargs):
183 if not isinstance(self, CardADF):
184 if not 'fid' in kwargs:
185 raise TypeError('fid is mandatory for all DF')
186 super().__init__(**kwargs)
187 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100188 self.shell_commands = [self.ShellCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +0100189
190 def __str__(self):
191 return "DF(%s)" % (super().__str__())
192
Harald Welteee3501f2021-04-02 13:00:18 +0200193 def add_file(self, child:CardFile, ignore_existing:bool=False):
194 """Add a child (DF/EF) to this DF.
195 Args:
196 child: The new DF/EF to be added
197 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
198 """
Harald Welteb2edd142021-01-08 23:29:35 +0100199 if not isinstance(child, CardFile):
200 raise TypeError("Expected a File instance")
Philipp Maier3aec8712021-03-09 21:49:01 +0100201 if not is_hex(child.fid, minlen = 4, maxlen = 4):
202 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100203 if child.name in CardFile.RESERVED_NAMES:
204 raise ValueError("File name %s is a reserved name" % (child.name))
205 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100206 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100207 if child.fid in self.children:
208 if ignore_existing:
209 return
210 raise ValueError("File with given fid %s already exists" % (child.fid))
211 if self.lookup_file_by_sfid(child.sfid):
212 raise ValueError("File with given sfid %s already exists" % (child.sfid))
213 if self.lookup_file_by_name(child.name):
214 if ignore_existing:
215 return
216 raise ValueError("File with given name %s already exists" % (child.name))
217 self.children[child.fid] = child
218 child.parent = self
219
Harald Welteee3501f2021-04-02 13:00:18 +0200220 def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
221 """Add a list of child (DF/EF) to this DF
222
223 Args:
224 children: List of new DF/EFs to be added
225 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
226 """
Harald Welteb2edd142021-01-08 23:29:35 +0100227 for child in children:
228 self.add_file(child, ignore_existing)
229
Harald Welteee3501f2021-04-02 13:00:18 +0200230 def get_selectables(self, flags = []) -> dict:
231 """Return a dict of {'identifier': File} that is selectable from the current DF.
232
233 Args:
234 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
235 If not specified, all selectables will be returned.
236 Returns:
237 dict containing all selectable items. Key is identifier (string), value
238 a reference to a CardFile (or derived class) instance.
239 """
Harald Welteb2edd142021-01-08 23:29:35 +0100240 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100241 sels = super().get_selectables(flags)
242 if flags == [] or 'FIDS' in flags:
243 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100244 if flags == [] or 'FNAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100245 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100246 return sels
247
Harald Welte1e456572021-04-02 17:16:30 +0200248 def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200249 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100250 if name == None:
251 return None
252 for i in self.children.values():
253 if i.name and i.name == name:
254 return i
255 return None
256
Harald Welte1e456572021-04-02 17:16:30 +0200257 def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200258 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100259 if sfid == None:
260 return None
261 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200262 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100263 return i
264 return None
265
Harald Welteee3501f2021-04-02 13:00:18 +0200266 def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
267 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100268 if fid in self.children:
269 return self.children[fid]
270 return None
271
272
273class CardMF(CardDF):
274 """MF (Master File) in the smart card filesystem"""
275 def __init__(self, **kwargs):
276 # can be overridden; use setdefault
277 kwargs.setdefault('fid', '3f00')
278 kwargs.setdefault('name', 'MF')
279 kwargs.setdefault('desc', 'Master File (directory root)')
280 # cannot be overridden; use assignment
281 kwargs['parent'] = self
282 super().__init__(**kwargs)
283 self.applications = dict()
284
285 def __str__(self):
286 return "MF(%s)" % (self.fid)
287
Harald Welte5ce35242021-04-02 20:27:05 +0200288 def add_application_df(self, app:'CardADF'):
289 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100290 if not isinstance(app, CardADF):
291 raise TypeError("Expected an ADF instance")
292 if app.aid in self.applications:
293 raise ValueError("AID %s already exists" % (app.aid))
294 self.applications[app.aid] = app
295 app.parent=self
296
297 def get_app_names(self):
298 """Get list of completions (AID names)"""
299 return [x.name for x in self.applications]
300
Harald Welteee3501f2021-04-02 13:00:18 +0200301 def get_selectables(self, flags = []) -> dict:
302 """Return a dict of {'identifier': File} that is selectable from the current DF.
303
304 Args:
305 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
306 If not specified, all selectables will be returned.
307 Returns:
308 dict containing all selectable items. Key is identifier (string), value
309 a reference to a CardFile (or derived class) instance.
310 """
Philipp Maier786f7812021-02-25 16:48:10 +0100311 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100312 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100313 return sels
314
Harald Welteee3501f2021-04-02 13:00:18 +0200315 def get_app_selectables(self, flags = []) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100316 """Get applications by AID + name"""
317 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100318 if flags == [] or 'AIDS' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100319 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100320 if flags == [] or 'ANAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100321 sels.update({x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100322 return sels
323
Harald Welteee3501f2021-04-02 13:00:18 +0200324 def decode_select_response(self, data_hex:str) -> Any:
325 """Decode the response to a SELECT command.
326
327 This is the fall-back method which doesn't perform any decoding. It mostly
328 exists so specific derived classes can overload it for actual decoding.
329 """
Harald Welteb2edd142021-01-08 23:29:35 +0100330 return data_hex
331
332
333
334class CardADF(CardDF):
335 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Welteee3501f2021-04-02 13:00:18 +0200336 def __init__(self, aid:str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100337 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200338 # reference to CardApplication may be set from CardApplication constructor
339 self.application:Optional[CardApplication] = None
Harald Welteb2edd142021-01-08 23:29:35 +0100340 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200341 mf = self.get_mf()
342 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200343 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100344
345 def __str__(self):
346 return "ADF(%s)" % (self.aid)
347
Harald Welteee3501f2021-04-02 13:00:18 +0200348 def _path_element(self, prefer_name:bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100349 if self.name and prefer_name:
350 return self.name
351 else:
352 return self.aid
353
354
355class CardEF(CardFile):
356 """EF (Entry File) in the smart card filesystem"""
357 def __init__(self, *, fid, **kwargs):
358 kwargs['fid'] = fid
359 super().__init__(**kwargs)
360
361 def __str__(self):
362 return "EF(%s)" % (super().__str__())
363
Harald Welteee3501f2021-04-02 13:00:18 +0200364 def get_selectables(self, flags = []) -> dict:
365 """Return a dict of {'identifier': File} that is selectable from the current DF.
366
367 Args:
368 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
369 If not specified, all selectables will be returned.
370 Returns:
371 dict containing all selectable items. Key is identifier (string), value
372 a reference to a CardFile (or derived class) instance.
373 """
Harald Welteb2edd142021-01-08 23:29:35 +0100374 #global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100375 sels = super().get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100376 sels.update({x.name:x for x in self.parent.children.values() if x != self})
377 return sels
378
379
380class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200381 """Transparent EF (Entry File) in the smart card filesystem.
382
383 A Transparent EF is a binary file with no formal structure. This is contrary to
384 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100385
386 @with_default_category('Transparent EF Commands')
387 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200388 """Shell commands specific for Trransparent EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100389 def __init__(self):
390 super().__init__()
391
392 read_bin_parser = argparse.ArgumentParser()
393 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
394 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
395 @cmd2.with_argparser(read_bin_parser)
396 def do_read_binary(self, opts):
397 """Read binary data from a transparent EF"""
398 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
399 self._cmd.poutput(data)
400
Harald Weltebcad86c2021-04-06 20:08:39 +0200401 read_bin_dec_parser = argparse.ArgumentParser()
402 read_bin_dec_parser.add_argument('--oneline', action='store_true',
403 help='No JSON pretty-printing, dump as a single line')
404 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100405 def do_read_binary_decoded(self, opts):
406 """Read + decode data from a transparent EF"""
407 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200408 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100409
410 upd_bin_parser = argparse.ArgumentParser()
411 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
412 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
413 @cmd2.with_argparser(upd_bin_parser)
414 def do_update_binary(self, opts):
415 """Update (Write) data of a transparent EF"""
416 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100417 if data:
418 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100419
420 upd_bin_dec_parser = argparse.ArgumentParser()
421 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200422 upd_bin_dec_parser.add_argument('--json-path', type=str,
423 help='JSON path to modify specific element of file only')
Harald Welteb2edd142021-01-08 23:29:35 +0100424 @cmd2.with_argparser(upd_bin_dec_parser)
425 def do_update_binary_decoded(self, opts):
426 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200427 if opts.json_path:
428 (data_json, sw) = self._cmd.rs.read_binary_dec()
429 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
430 else:
431 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100432 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100433 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200434 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100435
Harald Welteee3501f2021-04-02 13:00:18 +0200436 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
437 size={1,None}):
438 """
439 Args:
440 fid : File Identifier (4 hex digits)
441 sfid : Short File Identifier (2 hex digits, optional)
442 name : Brief name of the file, lik EF_ICCID
443 desc : Descriptoin of the file
444 parent : Parent CardFile object within filesystem hierarchy
445 size : tuple of (minimum_size, recommended_size)
446 """
Harald Welteb2edd142021-01-08 23:29:35 +0100447 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
448 self.size = size
449 self.shell_commands = [self.ShellCommands()]
450
Harald Welteee3501f2021-04-02 13:00:18 +0200451 def decode_bin(self, raw_bin_data:bytearray) -> dict:
452 """Decode raw (binary) data into abstract representation.
453
454 A derived class would typically provide a _decode_bin() or _decode_hex() method
455 for implementing this specifically for the given file. This function checks which
456 of the method exists, add calls them (with conversion, as needed).
457
458 Args:
459 raw_bin_data : binary encoded data
460 Returns:
461 abstract_data; dict representing the decoded data
462 """
Harald Welteb2edd142021-01-08 23:29:35 +0100463 method = getattr(self, '_decode_bin', None)
464 if callable(method):
465 return method(raw_bin_data)
466 method = getattr(self, '_decode_hex', None)
467 if callable(method):
468 return method(b2h(raw_bin_data))
469 return {'raw': raw_bin_data.hex()}
470
Harald Welteee3501f2021-04-02 13:00:18 +0200471 def decode_hex(self, raw_hex_data:str) -> dict:
472 """Decode raw (hex string) data into abstract representation.
473
474 A derived class would typically provide a _decode_bin() or _decode_hex() method
475 for implementing this specifically for the given file. This function checks which
476 of the method exists, add calls them (with conversion, as needed).
477
478 Args:
479 raw_hex_data : hex-encoded data
480 Returns:
481 abstract_data; dict representing the decoded data
482 """
Harald Welteb2edd142021-01-08 23:29:35 +0100483 method = getattr(self, '_decode_hex', None)
484 if callable(method):
485 return method(raw_hex_data)
486 raw_bin_data = h2b(raw_hex_data)
487 method = getattr(self, '_decode_bin', None)
488 if callable(method):
489 return method(raw_bin_data)
490 return {'raw': raw_bin_data.hex()}
491
Harald Welteee3501f2021-04-02 13:00:18 +0200492 def encode_bin(self, abstract_data:dict) -> bytearray:
493 """Encode abstract representation into raw (binary) data.
494
495 A derived class would typically provide an _encode_bin() or _encode_hex() method
496 for implementing this specifically for the given file. This function checks which
497 of the method exists, add calls them (with conversion, as needed).
498
499 Args:
500 abstract_data : dict representing the decoded data
501 Returns:
502 binary encoded data
503 """
Harald Welteb2edd142021-01-08 23:29:35 +0100504 method = getattr(self, '_encode_bin', None)
505 if callable(method):
506 return method(abstract_data)
507 method = getattr(self, '_encode_hex', None)
508 if callable(method):
509 return h2b(method(abstract_data))
510 raise NotImplementedError
511
Harald Welteee3501f2021-04-02 13:00:18 +0200512 def encode_hex(self, abstract_data:dict) -> str:
513 """Encode abstract representation into raw (hex string) data.
514
515 A derived class would typically provide an _encode_bin() or _encode_hex() method
516 for implementing this specifically for the given file. This function checks which
517 of the method exists, add calls them (with conversion, as needed).
518
519 Args:
520 abstract_data : dict representing the decoded data
521 Returns:
522 hex string encoded data
523 """
Harald Welteb2edd142021-01-08 23:29:35 +0100524 method = getattr(self, '_encode_hex', None)
525 if callable(method):
526 return method(abstract_data)
527 method = getattr(self, '_encode_bin', None)
528 if callable(method):
529 raw_bin_data = method(abstract_data)
530 return b2h(raw_bin_data)
531 raise NotImplementedError
532
533
534class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200535 """Linear Fixed EF (Entry File) in the smart card filesystem.
536
537 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
538 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100539
540 @with_default_category('Linear Fixed EF Commands')
541 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200542 """Shell commands specific for Linear Fixed EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100543 def __init__(self):
544 super().__init__()
545
546 read_rec_parser = argparse.ArgumentParser()
547 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100548 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 +0100549 @cmd2.with_argparser(read_rec_parser)
550 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100551 """Read one or multiple records from a record-oriented EF"""
552 for r in range(opts.count):
553 recnr = opts.record_nr + r
554 (data, sw) = self._cmd.rs.read_record(recnr)
555 if (len(data) > 0):
556 recstr = str(data)
557 else:
558 recstr = "(empty)"
559 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100560
561 read_rec_dec_parser = argparse.ArgumentParser()
562 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200563 read_rec_dec_parser.add_argument('--oneline', action='store_true',
564 help='No JSON pretty-printing, dump as a single line')
Harald Welteb2edd142021-01-08 23:29:35 +0100565 @cmd2.with_argparser(read_rec_dec_parser)
566 def do_read_record_decoded(self, opts):
567 """Read + decode a record from a record-oriented EF"""
568 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200569 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100570
571 upd_rec_parser = argparse.ArgumentParser()
572 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
573 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
574 @cmd2.with_argparser(upd_rec_parser)
575 def do_update_record(self, opts):
576 """Update (write) data to a record-oriented EF"""
577 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100578 if data:
579 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100580
581 upd_rec_dec_parser = argparse.ArgumentParser()
582 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
583 upd_rec_dec_parser.add_argument('data', help='Data bytes (hex format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200584 upd_rec_dec_parser.add_argument('--json-path', type=str,
585 help='JSON path to modify specific element of record only')
Harald Welteb2edd142021-01-08 23:29:35 +0100586 @cmd2.with_argparser(upd_rec_dec_parser)
587 def do_update_record_decoded(self, opts):
588 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200589 if opts.json_path:
590 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
591 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
592 else:
593 data_json = json.loads(opts.data)
594 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100595 if data:
596 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100597
Harald Welteee3501f2021-04-02 13:00:18 +0200598 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
599 parent:Optional[CardDF]=None, rec_len={1,None}):
600 """
601 Args:
602 fid : File Identifier (4 hex digits)
603 sfid : Short File Identifier (2 hex digits, optional)
604 name : Brief name of the file, lik EF_ICCID
605 desc : Descriptoin of the file
606 parent : Parent CardFile object within filesystem hierarchy
607 rec_len : tuple of (minimum_length, recommended_length)
608 """
Harald Welteb2edd142021-01-08 23:29:35 +0100609 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
610 self.rec_len = rec_len
611 self.shell_commands = [self.ShellCommands()]
612
Harald Welteee3501f2021-04-02 13:00:18 +0200613 def decode_record_hex(self, raw_hex_data:str) -> dict:
614 """Decode raw (hex string) data into abstract representation.
615
616 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
617 method for implementing this specifically for the given file. This function checks which
618 of the method exists, add calls them (with conversion, as needed).
619
620 Args:
621 raw_hex_data : hex-encoded data
622 Returns:
623 abstract_data; dict representing the decoded data
624 """
Harald Welteb2edd142021-01-08 23:29:35 +0100625 method = getattr(self, '_decode_record_hex', None)
626 if callable(method):
627 return method(raw_hex_data)
628 raw_bin_data = h2b(raw_hex_data)
629 method = getattr(self, '_decode_record_bin', None)
630 if callable(method):
631 return method(raw_bin_data)
632 return {'raw': raw_bin_data.hex()}
633
Harald Welteee3501f2021-04-02 13:00:18 +0200634 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
635 """Decode raw (binary) data into abstract representation.
636
637 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
638 method for implementing this specifically for the given file. This function checks which
639 of the method exists, add calls them (with conversion, as needed).
640
641 Args:
642 raw_bin_data : binary encoded data
643 Returns:
644 abstract_data; dict representing the decoded data
645 """
Harald Welteb2edd142021-01-08 23:29:35 +0100646 method = getattr(self, '_decode_record_bin', None)
647 if callable(method):
648 return method(raw_bin_data)
649 raw_hex_data = b2h(raw_bin_data)
650 method = getattr(self, '_decode_record_hex', None)
651 if callable(method):
652 return method(raw_hex_data)
653 return {'raw': raw_hex_data}
654
Harald Welteee3501f2021-04-02 13:00:18 +0200655 def encode_record_hex(self, abstract_data:dict) -> str:
656 """Encode abstract representation into raw (hex string) data.
657
658 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
659 method for implementing this specifically for the given file. This function checks which
660 of the method exists, add calls them (with conversion, as needed).
661
662 Args:
663 abstract_data : dict representing the decoded data
664 Returns:
665 hex string encoded data
666 """
Harald Welteb2edd142021-01-08 23:29:35 +0100667 method = getattr(self, '_encode_record_hex', None)
668 if callable(method):
669 return method(abstract_data)
670 method = getattr(self, '_encode_record_bin', None)
671 if callable(method):
672 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200673 return b2h(raw_bin_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100674 raise NotImplementedError
675
Harald Welteee3501f2021-04-02 13:00:18 +0200676 def encode_record_bin(self, abstract_data:dict) -> bytearray:
677 """Encode abstract representation into raw (binary) data.
678
679 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
680 method for implementing this specifically for the given file. This function checks which
681 of the method exists, add calls them (with conversion, as needed).
682
683 Args:
684 abstract_data : dict representing the decoded data
685 Returns:
686 binary encoded data
687 """
Harald Welteb2edd142021-01-08 23:29:35 +0100688 method = getattr(self, '_encode_record_bin', None)
689 if callable(method):
690 return method(abstract_data)
691 method = getattr(self, '_encode_record_hex', None)
692 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200693 return h2b(method(abstract_data))
Harald Welteb2edd142021-01-08 23:29:35 +0100694 raise NotImplementedError
695
696class CyclicEF(LinFixedEF):
697 """Cyclic EF (Entry File) in the smart card filesystem"""
698 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Welteee3501f2021-04-02 13:00:18 +0200699 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
700 rec_len={1,None}):
Harald Welteb2edd142021-01-08 23:29:35 +0100701 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
702
703class TransRecEF(TransparentEF):
704 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200705
Harald Welteb2edd142021-01-08 23:29:35 +0100706 These are the real odd-balls and mostly look like mistakes in the specification:
707 Specified as 'transparent' EF, but actually containing several fixed-length records
708 inside.
709 We add a special class for those, so the user only has to provide encoder/decoder functions
710 for a record, while this class takes care of split / merge of records.
711 """
Harald Welte1e456572021-04-02 17:16:30 +0200712 def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
713 parent:Optional[CardDF]=None, size={1,None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200714 """
715 Args:
716 fid : File Identifier (4 hex digits)
717 sfid : Short File Identifier (2 hex digits, optional)
718 name : Brief name of the file, lik EF_ICCID
719 desc : Descriptoin of the file
720 parent : Parent CardFile object within filesystem hierarchy
721 rec_len : Length of the fixed-length records within transparent EF
722 size : tuple of (minimum_size, recommended_size)
723 """
Harald Welteb2edd142021-01-08 23:29:35 +0100724 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
725 self.rec_len = rec_len
726
Harald Welteee3501f2021-04-02 13:00:18 +0200727 def decode_record_hex(self, raw_hex_data:str) -> dict:
728 """Decode raw (hex string) data into abstract representation.
729
730 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
731 method for implementing this specifically for the given file. This function checks which
732 of the method exists, add calls them (with conversion, as needed).
733
734 Args:
735 raw_hex_data : hex-encoded data
736 Returns:
737 abstract_data; dict representing the decoded data
738 """
Harald Welteb2edd142021-01-08 23:29:35 +0100739 method = getattr(self, '_decode_record_hex', None)
740 if callable(method):
741 return method(raw_hex_data)
742 method = getattr(self, '_decode_record_bin', None)
743 if callable(method):
744 raw_bin_data = h2b(raw_hex_data)
745 return method(raw_bin_data)
746 return {'raw': raw_hex_data}
747
Harald Welteee3501f2021-04-02 13:00:18 +0200748 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
749 """Decode raw (binary) data into abstract representation.
750
751 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
752 method for implementing this specifically for the given file. This function checks which
753 of the method exists, add calls them (with conversion, as needed).
754
755 Args:
756 raw_bin_data : binary encoded data
757 Returns:
758 abstract_data; dict representing the decoded data
759 """
Harald Welteb2edd142021-01-08 23:29:35 +0100760 method = getattr(self, '_decode_record_bin', None)
761 if callable(method):
762 return method(raw_bin_data)
763 raw_hex_data = b2h(raw_bin_data)
764 method = getattr(self, '_decode_record_hex', None)
765 if callable(method):
766 return method(raw_hex_data)
767 return {'raw': raw_hex_data}
768
Harald Welteee3501f2021-04-02 13:00:18 +0200769 def encode_record_hex(self, abstract_data:dict) -> str:
770 """Encode abstract representation into raw (hex string) data.
771
772 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
773 method for implementing this specifically for the given file. This function checks which
774 of the method exists, add calls them (with conversion, as needed).
775
776 Args:
777 abstract_data : dict representing the decoded data
778 Returns:
779 hex string encoded data
780 """
Harald Welteb2edd142021-01-08 23:29:35 +0100781 method = getattr(self, '_encode_record_hex', None)
782 if callable(method):
783 return method(abstract_data)
784 method = getattr(self, '_encode_record_bin', None)
785 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +0200786 return b2h(method(abstract_data))
Harald Welteb2edd142021-01-08 23:29:35 +0100787 raise NotImplementedError
788
Harald Welteee3501f2021-04-02 13:00:18 +0200789 def encode_record_bin(self, abstract_data:dict) -> bytearray:
790 """Encode abstract representation into raw (binary) data.
791
792 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
793 method for implementing this specifically for the given file. This function checks which
794 of the method exists, add calls them (with conversion, as needed).
795
796 Args:
797 abstract_data : dict representing the decoded data
798 Returns:
799 binary encoded data
800 """
Harald Welteb2edd142021-01-08 23:29:35 +0100801 method = getattr(self, '_encode_record_bin', None)
802 if callable(method):
803 return method(abstract_data)
804 method = getattr(self, '_encode_record_hex', None)
805 if callable(method):
806 return h2b(method(abstract_data))
807 raise NotImplementedError
808
Harald Welteee3501f2021-04-02 13:00:18 +0200809 def _decode_bin(self, raw_bin_data:bytearray):
Harald Welteb2edd142021-01-08 23:29:35 +0100810 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
811 return [self.decode_record_bin(x) for x in chunks]
812
Harald Welteee3501f2021-04-02 13:00:18 +0200813 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +0100814 chunks = [self.encode_record_bin(x) for x in abstract_data]
815 # FIXME: pad to file size
816 return b''.join(chunks)
817
818
819
820
821
822class RuntimeState(object):
823 """Represent the runtime state of a session with a card."""
Harald Welteee3501f2021-04-02 13:00:18 +0200824 def __init__(self, card, profile:'CardProfile'):
825 """
826 Args:
827 card : pysim.cards.Card instance
828 profile : CardProfile instance
829 """
Harald Welteb2edd142021-01-08 23:29:35 +0100830 self.mf = CardMF()
831 self.card = card
Harald Welte5ce35242021-04-02 20:27:05 +0200832 self.selected_file:CardDF = self.mf
Harald Welteb2edd142021-01-08 23:29:35 +0100833 self.profile = profile
Harald Welte5ce35242021-04-02 20:27:05 +0200834 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +0100835 apps = self._match_applications()
836 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +0200837 if a.adf:
838 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +0100839 for f in self.profile.files_in_mf:
840 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +0100841 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +0100842
Philipp Maier1e896f32021-03-10 17:02:53 +0100843 def _match_applications(self):
844 """match the applications from the profile with applications on the card"""
845 apps_profile = self.profile.applications
846 aids_card = self.card.read_aids()
847 apps_taken = []
848 if aids_card:
849 aids_taken = []
850 print("AIDs on card:")
851 for a in aids_card:
852 for f in apps_profile:
853 if f.aid in a:
854 print(" %s: %s" % (f.name, a))
855 aids_taken.append(a)
856 apps_taken.append(f)
857 aids_unknown = set(aids_card) - set(aids_taken)
858 for a in aids_unknown:
859 print(" unknown: %s" % a)
860 else:
861 print("error: could not determine card applications")
862 return apps_taken
863
Harald Welteee3501f2021-04-02 13:00:18 +0200864 def get_cwd(self) -> CardDF:
865 """Obtain the current working directory.
866
867 Returns:
868 CardDF instance
869 """
Harald Welteb2edd142021-01-08 23:29:35 +0100870 if isinstance(self.selected_file, CardDF):
871 return self.selected_file
872 else:
873 return self.selected_file.parent
874
Harald Welte5ce35242021-04-02 20:27:05 +0200875 def get_application_df(self) -> Optional[CardADF]:
876 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +0200877
878 Returns:
879 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +0100880 # iterate upwards from selected file; check if any is an ADF
881 node = self.selected_file
882 while node.parent != node:
883 if isinstance(node, CardADF):
884 return node
885 node = node.parent
886 return None
887
Harald Welteee3501f2021-04-02 13:00:18 +0200888 def interpret_sw(self, sw:str):
889 """Interpret a given status word relative to the currently selected application
890 or the underlying card profile.
891
892 Args:
893 sw : Status word as string of 4 hexd digits
894
895 Returns:
896 Tuple of two strings
897 """
Harald Welte86fbd392021-04-02 22:13:09 +0200898 res = None
Harald Welte5ce35242021-04-02 20:27:05 +0200899 adf = self.get_application_df()
900 if adf:
901 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +0100902 # The application either comes with its own interpret_sw
903 # method or we will use the interpret_sw method from the
904 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +0200905 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +0200906 res = app.interpret_sw(sw)
907 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +0100908
Harald Welteee3501f2021-04-02 13:00:18 +0200909 def probe_file(self, fid:str, cmd_app=None):
910 """Blindly try to select a file and automatically add a matching file
911 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100912 if not is_hex(fid, 4, 4):
913 raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
914
915 try:
916 (data, sw) = self.card._scc.select_file(fid)
917 except SwMatchError as swm:
918 k = self.interpret_sw(swm.sw_actual)
919 if not k:
920 raise(swm)
921 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
922
923 select_resp = self.selected_file.decode_select_response(data)
924 if (select_resp['file_descriptor']['file_type'] == 'df'):
925 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
926 else:
927 if (select_resp['file_descriptor']['structure'] == 'transparent'):
928 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementry file, manually added at runtime")
929 else:
930 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementry file, manually added at runtime")
931
932 self.selected_file.add_files([f])
933 self.selected_file = f
934 return select_resp
935
Harald Welteee3501f2021-04-02 13:00:18 +0200936 def select(self, name:str, cmd_app=None):
937 """Select a file (EF, DF, ADF, MF, ...).
938
939 Args:
940 name : Name of file to select
941 cmd_app : Command Application State (for unregistering old file commands)
942 """
Harald Welteb2edd142021-01-08 23:29:35 +0100943 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +0100944 if is_hex(name):
945 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +0100946
947 # unregister commands of old file
948 if cmd_app and self.selected_file.shell_commands:
949 for c in self.selected_file.shell_commands:
950 cmd_app.unregister_command_set(c)
951
Harald Welteb2edd142021-01-08 23:29:35 +0100952 if name in sels:
953 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +0100954 try:
955 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +0100956 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +0100957 else:
958 (data, sw) = self.card._scc.select_file(f.fid)
959 self.selected_file = f
960 except SwMatchError as swm:
961 k = self.interpret_sw(swm.sw_actual)
962 if not k:
963 raise(swm)
964 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +0100965 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100966 else:
Philipp Maier63f572d2021-03-09 22:42:47 +0100967 select_resp = self.probe_file(name, cmd_app)
968
969 # register commands of new file
970 if cmd_app and self.selected_file.shell_commands:
971 for c in self.selected_file.shell_commands:
972 cmd_app.register_command_set(c)
973
974 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +0100975
Harald Welteee3501f2021-04-02 13:00:18 +0200976 def read_binary(self, length:int=None, offset:int=0):
977 """Read [part of] a transparent EF binary data.
978
979 Args:
980 length : Amount of data to read (None: as much as possible)
981 offset : Offset into the file from which to read 'length' bytes
982 Returns:
983 binary data read from the file
984 """
Harald Welteb2edd142021-01-08 23:29:35 +0100985 if not isinstance(self.selected_file, TransparentEF):
986 raise TypeError("Only works with TransparentEF")
987 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
988
Harald Welte2d4a64b2021-04-03 09:01:24 +0200989 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200990 """Read [part of] a transparent EF binary data and decode it.
991
992 Args:
993 length : Amount of data to read (None: as much as possible)
994 offset : Offset into the file from which to read 'length' bytes
995 Returns:
996 abstract decode data read from the file
997 """
Harald Welteb2edd142021-01-08 23:29:35 +0100998 (data, sw) = self.read_binary()
999 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001000 return (dec_data, sw)
1001
Harald Welteee3501f2021-04-02 13:00:18 +02001002 def update_binary(self, data_hex:str, offset:int=0):
1003 """Update transparent EF binary data.
1004
1005 Args:
1006 data_hex : hex string of data to be written
1007 offset : Offset into the file from which to write 'data_hex'
1008 """
Harald Welteb2edd142021-01-08 23:29:35 +01001009 if not isinstance(self.selected_file, TransparentEF):
1010 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001011 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001012
Harald Welteee3501f2021-04-02 13:00:18 +02001013 def update_binary_dec(self, data:dict):
1014 """Update transparent EF from abstract data. Encodes the data to binary and
1015 then updates the EF with it.
1016
1017 Args:
1018 data : abstract data which is to be encoded and written
1019 """
Harald Welteb2edd142021-01-08 23:29:35 +01001020 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001021 return self.update_binary(data_hex)
1022
Harald Welteee3501f2021-04-02 13:00:18 +02001023 def read_record(self, rec_nr:int=0):
1024 """Read a record as binary data.
1025
1026 Args:
1027 rec_nr : Record number to read
1028 Returns:
1029 hex string of binary data contained in record
1030 """
Harald Welteb2edd142021-01-08 23:29:35 +01001031 if not isinstance(self.selected_file, LinFixedEF):
1032 raise TypeError("Only works with Linear Fixed EF")
1033 # returns a string of hex nibbles
1034 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1035
Harald Welteee3501f2021-04-02 13:00:18 +02001036 def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
1037 """Read a record and decode it to abstract data.
1038
1039 Args:
1040 rec_nr : Record number to read
1041 Returns:
1042 abstract data contained in record
1043 """
Harald Welteb2edd142021-01-08 23:29:35 +01001044 (data, sw) = self.read_record(rec_nr)
1045 return (self.selected_file.decode_record_hex(data), sw)
1046
Harald Welteee3501f2021-04-02 13:00:18 +02001047 def update_record(self, rec_nr:int, data_hex:str):
1048 """Update a record with given binary data
1049
1050 Args:
1051 rec_nr : Record number to read
1052 data_hex : Hex string binary data to be written
1053 """
Harald Welteb2edd142021-01-08 23:29:35 +01001054 if not isinstance(self.selected_file, LinFixedEF):
1055 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001056 return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001057
Harald Welteee3501f2021-04-02 13:00:18 +02001058 def update_record_dec(self, rec_nr:int, data:dict):
1059 """Update a record with given abstract data. Will encode abstract to binary data
1060 and then write it to the given record on the card.
1061
1062 Args:
1063 rec_nr : Record number to read
1064 data_hex : Abstract data to be written
1065 """
Harald Welte1e456572021-04-02 17:16:30 +02001066 data_hex = self.selected_file.encode_record_hex(data)
1067 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001068
1069
1070
1071class FileData(object):
1072 """Represent the runtime, on-card data."""
1073 def __init__(self, fdesc):
1074 self.desc = fdesc
1075 self.fcp = None
1076
1077
Harald Welteee3501f2021-04-02 13:00:18 +02001078def interpret_sw(sw_data:dict, sw:str):
1079 """Interpret a given status word.
1080
1081 Args:
1082 sw_data : Hierarchical dict of status word matches
1083 sw : status word to match (string of 4 hex digits)
1084 Returns:
1085 tuple of two strings (class_string, description)
1086 """
Harald Welteb2edd142021-01-08 23:29:35 +01001087 for class_str, swdict in sw_data.items():
1088 # first try direct match
1089 if sw in swdict:
1090 return (class_str, swdict[sw])
1091 # next try wildcard matches
1092 for pattern, descr in swdict.items():
1093 if sw_match(sw, pattern):
1094 return (class_str, descr)
1095 return None
1096
1097class CardApplication(object):
1098 """A card application is represented by an ADF (with contained hierarchy) and optionally
1099 some SW definitions."""
Harald Welte5ce35242021-04-02 20:27:05 +02001100 def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001101 """
1102 Args:
1103 adf : ADF name
1104 sw : Dict of status word conversions
1105 """
Harald Welteb2edd142021-01-08 23:29:35 +01001106 self.name = name
1107 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001108 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001109 # back-reference from ADF to Applicaiton
1110 if self.adf:
1111 self.aid = aid or self.adf.aid
1112 self.adf.application = self
1113 else:
1114 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001115
1116 def __str__(self):
1117 return "APP(%s)" % (self.name)
1118
1119 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001120 """Interpret a given status word within the application.
1121
1122 Args:
1123 sw : Status word as string of 4 hexd digits
1124
1125 Returns:
1126 Tuple of two strings
1127 """
Harald Welteb2edd142021-01-08 23:29:35 +01001128 return interpret_sw(self.sw, sw)
1129
1130class CardProfile(object):
1131 """A Card Profile describes a card, it's filessystem hierarchy, an [initial] list of
1132 applications as well as profile-specific SW and shell commands. Every card has
1133 one card profile, but there may be multiple applications within that profile."""
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001134 def __init__(self, name, **kw):
Harald Welteee3501f2021-04-02 13:00:18 +02001135 """
1136 Args:
1137 desc (str) : Description
1138 files_in_mf : List of CardEF instances present in MF
1139 applications : List of CardApplications present on card
1140 sw : List of status word definitions
1141 shell_cmdsets : List of cmd2 shell command sets of profile-specific commands
1142 """
Harald Welteb2edd142021-01-08 23:29:35 +01001143 self.name = name
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001144 self.desc = kw.get("desc", None)
1145 self.files_in_mf = kw.get("files_in_mf", [])
1146 self.sw = kw.get("sw", [])
1147 self.applications = kw.get("applications", [])
1148 self.shell_cmdsets = kw.get("shell_cmdsets", [])
Harald Welteb2edd142021-01-08 23:29:35 +01001149
1150 def __str__(self):
1151 return self.name
1152
Harald Welteee3501f2021-04-02 13:00:18 +02001153 def add_application(self, app:CardApplication):
1154 """Add an application to a card profile.
1155
1156 Args:
1157 app : CardApplication instance to be added to profile
1158 """
Philipp Maiereb72fa42021-03-26 21:29:57 +01001159 self.applications.append(app)
Harald Welteb2edd142021-01-08 23:29:35 +01001160
Harald Welteee3501f2021-04-02 13:00:18 +02001161 def interpret_sw(self, sw:str):
1162 """Interpret a given status word within the profile.
1163
1164 Args:
1165 sw : Status word as string of 4 hexd digits
1166
1167 Returns:
1168 Tuple of two strings
1169 """
Harald Welteb2edd142021-01-08 23:29:35 +01001170 return interpret_sw(self.sw, sw)