blob: 3e9d52e8c1789cbab19d0e9c18f1108a8c0ff472 [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
Harald Welte4145d3c2021-04-08 20:34:13 +020028import tempfile
Harald Welteb2edd142021-01-08 23:29:35 +010029import json
Harald Weltef44256c2021-10-14 15:53:39 +020030import abc
31import inspect
Harald Welteb2edd142021-01-08 23:29:35 +010032
33import cmd2
34from cmd2 import CommandSet, with_default_category, with_argparser
35import argparse
36
Philipp Maier9e42e7f2021-11-16 15:46:42 +010037from typing import cast, Optional, Iterable, List, Dict, Tuple
Harald Welteee3501f2021-04-02 13:00:18 +020038
Harald Weltef44256c2021-10-14 15:53:39 +020039from smartcard.util import toBytes
40
Harald Weltedaf2b392021-05-03 23:17:29 +020041from pySim.utils import sw_match, h2b, b2h, i2h, is_hex, auto_int, bertlv_parse_one, Hexstr
Harald Welte07c7b1f2021-05-28 22:01:29 +020042from pySim.construct import filter_dict, parse_construct
Harald Welteb2edd142021-01-08 23:29:35 +010043from pySim.exceptions import *
Harald Welte0d4e98a2021-04-07 00:14:40 +020044from pySim.jsonpath import js_path_find, js_path_modify
Harald Weltef44256c2021-10-14 15:53:39 +020045from pySim.commands import SimCardCommands
Harald Welteb2edd142021-01-08 23:29:35 +010046
47class CardFile(object):
48 """Base class for all objects in the smart card filesystem.
49 Serve as a common ancestor to all other file types; rarely used directly.
50 """
51 RESERVED_NAMES = ['..', '.', '/', 'MF']
52 RESERVED_FIDS = ['3f00']
53
Harald Welteee3501f2021-04-02 13:00:18 +020054 def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None,
Philipp Maier5af7bdf2021-11-04 12:48:41 +010055 parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None):
Harald Welteee3501f2021-04-02 13:00:18 +020056 """
57 Args:
58 fid : File Identifier (4 hex digits)
59 sfid : Short File Identifier (2 hex digits, optional)
60 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020061 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020062 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier5af7bdf2021-11-04 12:48:41 +010063 profile : Card profile that this file should be part of
Harald Welteee3501f2021-04-02 13:00:18 +020064 """
Harald Welteb2edd142021-01-08 23:29:35 +010065 if not isinstance(self, CardADF) and fid == None:
66 raise ValueError("fid is mandatory")
67 if fid:
68 fid = fid.lower()
69 self.fid = fid # file identifier
70 self.sfid = sfid # short file identifier
71 self.name = name # human readable name
72 self.desc = desc # human readable description
73 self.parent = parent
74 if self.parent and self.parent != self and self.fid:
75 self.parent.add_file(self)
Philipp Maier5af7bdf2021-11-04 12:48:41 +010076 self.profile = profile
Vadim Yanitskiya0040792021-04-09 22:44:44 +020077 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +010078
Philipp Maier66061582021-03-09 21:57:57 +010079 # Note: the basic properties (fid, name, ect.) are verified when
80 # the file is attached to a parent file. See method add_file() in
81 # class Card DF
82
Harald Welteb2edd142021-01-08 23:29:35 +010083 def __str__(self):
84 if self.name:
85 return self.name
86 else:
87 return self.fid
88
Harald Welteee3501f2021-04-02 13:00:18 +020089 def _path_element(self, prefer_name:bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +010090 if prefer_name and self.name:
91 return self.name
92 else:
93 return self.fid
94
Harald Welte1e456572021-04-02 17:16:30 +020095 def fully_qualified_path(self, prefer_name:bool=True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +020096 """Return fully qualified path to file as list of FID or name strings.
97
98 Args:
99 prefer_name : Preferably build path of names; fall-back to FIDs as required
100 """
Harald Welte1e456572021-04-02 17:16:30 +0200101 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +0100102 ret = self.parent.fully_qualified_path(prefer_name)
103 else:
104 ret = []
Harald Welte1e456572021-04-02 17:16:30 +0200105 elem = self._path_element(prefer_name)
106 if elem:
107 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100108 return ret
109
Harald Welteee3501f2021-04-02 13:00:18 +0200110 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100111 """Return the MF (root) of the file system."""
112 if self.parent == None:
113 return None
114 # iterate towards the top. MF has parent == self
115 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200116 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100117 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200118 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100119
Harald Welte1e456572021-04-02 17:16:30 +0200120 def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200121 """Return a dict of {'identifier': self} tuples.
122
123 Args:
124 alias : Add an alias with given name to 'self'
125 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
126 If not specified, all selectables will be returned.
127 Returns:
128 dict containing reference to 'self' for all identifiers.
129 """
Harald Welteb2edd142021-01-08 23:29:35 +0100130 sels = {}
131 if alias:
132 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100133 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100134 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100135 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100136 sels.update({self.name: self})
137 return sels
138
Harald Welte1e456572021-04-02 17:16:30 +0200139 def get_selectables(self, flags = []) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200140 """Return a dict of {'identifier': File} that is selectable from the current file.
141
142 Args:
143 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
144 If not specified, all selectables will be returned.
145 Returns:
146 dict containing all selectable items. Key is identifier (string), value
147 a reference to a CardFile (or derived class) instance.
148 """
Philipp Maier786f7812021-02-25 16:48:10 +0100149 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100150 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100151 if flags == [] or 'SELF' in flags:
152 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100153 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100154 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200155 if self.parent:
156 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100157 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100158 if flags == [] or 'MF' in flags:
159 mf = self.get_mf()
160 if mf:
161 sels.update(mf._get_self_selectables(flags = flags))
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100162 sels.update(mf.get_app_selectables(flags = flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100163 return sels
164
Harald Welte1e456572021-04-02 17:16:30 +0200165 def get_selectable_names(self, flags = []) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200166 """Return a dict of {'identifier': File} that is selectable from the current file.
167
168 Args:
169 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
170 If not specified, all selectables will be returned.
171 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200172 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200173 """
Philipp Maier786f7812021-02-25 16:48:10 +0100174 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100175 sel_keys = list(sels.keys())
176 sel_keys.sort()
177 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100178
Harald Welteee3501f2021-04-02 13:00:18 +0200179 def decode_select_response(self, data_hex:str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100180 """Decode the response to a SELECT command.
181
182 Args:
183 data_hex: Hex string of the select response
184 """
185
186 # When the current file does not implement a custom select response decoder,
187 # we just ask the parent file to decode the select response. If this method
188 # is not overloaded by the current file we will again ask the parent file.
189 # This way we recursively travel up the file system tree until we hit a file
190 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200191 if self.parent:
192 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100193
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100194 def get_profile(self):
195 """Get the profile associated with this file. If this file does not have any
196 profile assigned, try to find a file above (usually the MF) in the filesystem
197 hirarchy that has a profile assigned
198 """
199
200 # If we have a profile set, return it
201 if self.profile:
202 return self.profile
203
204 # Walk up recursively until we hit a parent that has a profile set
205 if self.parent:
206 return self.parent.get_profile()
207 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100208
209class CardDF(CardFile):
210 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100211
212 @with_default_category('DF/ADF Commands')
213 class ShellCommands(CommandSet):
214 def __init__(self):
215 super().__init__()
216
Harald Welteb2edd142021-01-08 23:29:35 +0100217 def __init__(self, **kwargs):
218 if not isinstance(self, CardADF):
219 if not 'fid' in kwargs:
220 raise TypeError('fid is mandatory for all DF')
221 super().__init__(**kwargs)
222 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100223 self.shell_commands = [self.ShellCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +0100224
225 def __str__(self):
226 return "DF(%s)" % (super().__str__())
227
Harald Welteee3501f2021-04-02 13:00:18 +0200228 def add_file(self, child:CardFile, ignore_existing:bool=False):
229 """Add a child (DF/EF) to this DF.
230 Args:
231 child: The new DF/EF to be added
232 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
233 """
Harald Welteb2edd142021-01-08 23:29:35 +0100234 if not isinstance(child, CardFile):
235 raise TypeError("Expected a File instance")
Philipp Maier3aec8712021-03-09 21:49:01 +0100236 if not is_hex(child.fid, minlen = 4, maxlen = 4):
237 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100238 if child.name in CardFile.RESERVED_NAMES:
239 raise ValueError("File name %s is a reserved name" % (child.name))
240 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100241 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100242 if child.fid in self.children:
243 if ignore_existing:
244 return
Harald Welte977035c2021-04-21 11:01:26 +0200245 raise ValueError("File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100246 if self.lookup_file_by_sfid(child.sfid):
Harald Welte977035c2021-04-21 11:01:26 +0200247 raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100248 if self.lookup_file_by_name(child.name):
249 if ignore_existing:
250 return
Harald Welte977035c2021-04-21 11:01:26 +0200251 raise ValueError("File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100252 self.children[child.fid] = child
253 child.parent = self
254
Harald Welteee3501f2021-04-02 13:00:18 +0200255 def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
256 """Add a list of child (DF/EF) to this DF
257
258 Args:
259 children: List of new DF/EFs to be added
260 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
261 """
Harald Welteb2edd142021-01-08 23:29:35 +0100262 for child in children:
263 self.add_file(child, ignore_existing)
264
Harald Welteee3501f2021-04-02 13:00:18 +0200265 def get_selectables(self, flags = []) -> dict:
266 """Return a dict of {'identifier': File} that is selectable from the current DF.
267
268 Args:
269 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
270 If not specified, all selectables will be returned.
271 Returns:
272 dict containing all selectable items. Key is identifier (string), value
273 a reference to a CardFile (or derived class) instance.
274 """
Harald Welteb2edd142021-01-08 23:29:35 +0100275 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100276 sels = super().get_selectables(flags)
277 if flags == [] or 'FIDS' in flags:
278 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100279 if flags == [] or 'FNAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100280 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100281 return sels
282
Harald Welte1e456572021-04-02 17:16:30 +0200283 def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200284 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100285 if name == None:
286 return None
287 for i in self.children.values():
288 if i.name and i.name == name:
289 return i
290 return None
291
Harald Welte1e456572021-04-02 17:16:30 +0200292 def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200293 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100294 if sfid == None:
295 return None
296 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200297 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100298 return i
299 return None
300
Harald Welteee3501f2021-04-02 13:00:18 +0200301 def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
302 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100303 if fid in self.children:
304 return self.children[fid]
305 return None
306
307
308class CardMF(CardDF):
309 """MF (Master File) in the smart card filesystem"""
310 def __init__(self, **kwargs):
311 # can be overridden; use setdefault
312 kwargs.setdefault('fid', '3f00')
313 kwargs.setdefault('name', 'MF')
314 kwargs.setdefault('desc', 'Master File (directory root)')
315 # cannot be overridden; use assignment
316 kwargs['parent'] = self
317 super().__init__(**kwargs)
318 self.applications = dict()
319
320 def __str__(self):
321 return "MF(%s)" % (self.fid)
322
Harald Welte5ce35242021-04-02 20:27:05 +0200323 def add_application_df(self, app:'CardADF'):
324 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100325 if not isinstance(app, CardADF):
326 raise TypeError("Expected an ADF instance")
327 if app.aid in self.applications:
328 raise ValueError("AID %s already exists" % (app.aid))
329 self.applications[app.aid] = app
330 app.parent=self
331
332 def get_app_names(self):
333 """Get list of completions (AID names)"""
334 return [x.name for x in self.applications]
335
Harald Welteee3501f2021-04-02 13:00:18 +0200336 def get_selectables(self, flags = []) -> dict:
337 """Return a dict of {'identifier': File} that is selectable from the current DF.
338
339 Args:
340 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
341 If not specified, all selectables will be returned.
342 Returns:
343 dict containing all selectable items. Key is identifier (string), value
344 a reference to a CardFile (or derived class) instance.
345 """
Philipp Maier786f7812021-02-25 16:48:10 +0100346 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100347 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100348 return sels
349
Harald Welteee3501f2021-04-02 13:00:18 +0200350 def get_app_selectables(self, flags = []) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100351 """Get applications by AID + name"""
352 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100353 if flags == [] or 'AIDS' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100354 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100355 if flags == [] or 'ANAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100356 sels.update({x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100357 return sels
358
Philipp Maier9e42e7f2021-11-16 15:46:42 +0100359 def decode_select_response(self, data_hex:str) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200360 """Decode the response to a SELECT command.
361
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100362 This is the fall-back method which automatically defers to the standard decoding
363 method defined by the card profile. When no profile is set, then no decoding is
364 performed. Specific derived classes (usually ADF) can overload this method to
365 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200366 """
Harald Welteb2edd142021-01-08 23:29:35 +0100367
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100368 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100369
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100370 if profile:
371 return profile.decode_select_response(data_hex)
372 else:
373 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100374
375class CardADF(CardDF):
376 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Welteee3501f2021-04-02 13:00:18 +0200377 def __init__(self, aid:str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100378 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200379 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200380 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100381 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200382 mf = self.get_mf()
383 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200384 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100385
386 def __str__(self):
387 return "ADF(%s)" % (self.aid)
388
Harald Welteee3501f2021-04-02 13:00:18 +0200389 def _path_element(self, prefer_name:bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100390 if self.name and prefer_name:
391 return self.name
392 else:
393 return self.aid
394
395
396class CardEF(CardFile):
397 """EF (Entry File) in the smart card filesystem"""
398 def __init__(self, *, fid, **kwargs):
399 kwargs['fid'] = fid
400 super().__init__(**kwargs)
401
402 def __str__(self):
403 return "EF(%s)" % (super().__str__())
404
Harald Welteee3501f2021-04-02 13:00:18 +0200405 def get_selectables(self, flags = []) -> dict:
406 """Return a dict of {'identifier': File} that is selectable from the current DF.
407
408 Args:
409 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
410 If not specified, all selectables will be returned.
411 Returns:
412 dict containing all selectable items. Key is identifier (string), value
413 a reference to a CardFile (or derived class) instance.
414 """
Harald Welteb2edd142021-01-08 23:29:35 +0100415 #global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100416 sels = super().get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100417 sels.update({x.name:x for x in self.parent.children.values() if x != self})
418 return sels
419
420
421class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200422 """Transparent EF (Entry File) in the smart card filesystem.
423
424 A Transparent EF is a binary file with no formal structure. This is contrary to
425 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100426
427 @with_default_category('Transparent EF Commands')
428 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200429 """Shell commands specific for transparent EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100430 def __init__(self):
431 super().__init__()
432
433 read_bin_parser = argparse.ArgumentParser()
434 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
435 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
436 @cmd2.with_argparser(read_bin_parser)
437 def do_read_binary(self, opts):
438 """Read binary data from a transparent EF"""
439 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
440 self._cmd.poutput(data)
441
Harald Weltebcad86c2021-04-06 20:08:39 +0200442 read_bin_dec_parser = argparse.ArgumentParser()
443 read_bin_dec_parser.add_argument('--oneline', action='store_true',
444 help='No JSON pretty-printing, dump as a single line')
445 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100446 def do_read_binary_decoded(self, opts):
447 """Read + decode data from a transparent EF"""
448 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200449 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100450
451 upd_bin_parser = argparse.ArgumentParser()
452 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
453 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
454 @cmd2.with_argparser(upd_bin_parser)
455 def do_update_binary(self, opts):
456 """Update (Write) data of a transparent EF"""
457 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100458 if data:
459 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100460
461 upd_bin_dec_parser = argparse.ArgumentParser()
462 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200463 upd_bin_dec_parser.add_argument('--json-path', type=str,
464 help='JSON path to modify specific element of file only')
Harald Welteb2edd142021-01-08 23:29:35 +0100465 @cmd2.with_argparser(upd_bin_dec_parser)
466 def do_update_binary_decoded(self, opts):
467 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200468 if opts.json_path:
469 (data_json, sw) = self._cmd.rs.read_binary_dec()
470 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
471 else:
472 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100473 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100474 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200475 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100476
Harald Welte4145d3c2021-04-08 20:34:13 +0200477 def do_edit_binary_decoded(self, opts):
478 """Edit the JSON representation of the EF contents in an editor."""
479 (orig_json, sw) = self._cmd.rs.read_binary_dec()
480 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
481 filename = '%s/file' % dirname
482 # write existing data as JSON to file
483 with open(filename, 'w') as text_file:
484 json.dump(orig_json, text_file, indent=4)
485 # run a text editor
486 self._cmd._run_editor(filename)
487 with open(filename, 'r') as text_file:
488 edited_json = json.load(text_file)
489 if edited_json == orig_json:
490 self._cmd.poutput("Data not modified, skipping write")
491 else:
492 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
493 if data:
494 self._cmd.poutput_json(data)
495
496
Harald Welteee3501f2021-04-02 13:00:18 +0200497 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
498 size={1,None}):
499 """
500 Args:
501 fid : File Identifier (4 hex digits)
502 sfid : Short File Identifier (2 hex digits, optional)
503 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200504 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200505 parent : Parent CardFile object within filesystem hierarchy
506 size : tuple of (minimum_size, recommended_size)
507 """
Harald Welteb2edd142021-01-08 23:29:35 +0100508 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200509 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200510 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100511 self.size = size
512 self.shell_commands = [self.ShellCommands()]
513
Harald Welteee3501f2021-04-02 13:00:18 +0200514 def decode_bin(self, raw_bin_data:bytearray) -> dict:
515 """Decode raw (binary) data into abstract representation.
516
517 A derived class would typically provide a _decode_bin() or _decode_hex() method
518 for implementing this specifically for the given file. This function checks which
519 of the method exists, add calls them (with conversion, as needed).
520
521 Args:
522 raw_bin_data : binary encoded data
523 Returns:
524 abstract_data; dict representing the decoded data
525 """
Harald Welteb2edd142021-01-08 23:29:35 +0100526 method = getattr(self, '_decode_bin', None)
527 if callable(method):
528 return method(raw_bin_data)
529 method = getattr(self, '_decode_hex', None)
530 if callable(method):
531 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200532 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200533 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200534 elif self._tlv:
535 self._tlv.from_tlv(raw_bin_data)
536 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100537 return {'raw': raw_bin_data.hex()}
538
Harald Welteee3501f2021-04-02 13:00:18 +0200539 def decode_hex(self, raw_hex_data:str) -> dict:
540 """Decode raw (hex string) data into abstract representation.
541
542 A derived class would typically provide a _decode_bin() or _decode_hex() method
543 for implementing this specifically for the given file. This function checks which
544 of the method exists, add calls them (with conversion, as needed).
545
546 Args:
547 raw_hex_data : hex-encoded data
548 Returns:
549 abstract_data; dict representing the decoded data
550 """
Harald Welteb2edd142021-01-08 23:29:35 +0100551 method = getattr(self, '_decode_hex', None)
552 if callable(method):
553 return method(raw_hex_data)
554 raw_bin_data = h2b(raw_hex_data)
555 method = getattr(self, '_decode_bin', None)
556 if callable(method):
557 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200558 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200559 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200560 elif self._tlv:
561 self._tlv.from_tlv(raw_bin_data)
562 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100563 return {'raw': raw_bin_data.hex()}
564
Harald Welteee3501f2021-04-02 13:00:18 +0200565 def encode_bin(self, abstract_data:dict) -> bytearray:
566 """Encode abstract representation into raw (binary) data.
567
568 A derived class would typically provide an _encode_bin() or _encode_hex() method
569 for implementing this specifically for the given file. This function checks which
570 of the method exists, add calls them (with conversion, as needed).
571
572 Args:
573 abstract_data : dict representing the decoded data
574 Returns:
575 binary encoded data
576 """
Harald Welteb2edd142021-01-08 23:29:35 +0100577 method = getattr(self, '_encode_bin', None)
578 if callable(method):
579 return method(abstract_data)
580 method = getattr(self, '_encode_hex', None)
581 if callable(method):
582 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200583 if self._construct:
584 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200585 elif self._tlv:
586 self._tlv.from_dict(abstract_data)
587 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200588 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100589
Harald Welteee3501f2021-04-02 13:00:18 +0200590 def encode_hex(self, abstract_data:dict) -> str:
591 """Encode abstract representation into raw (hex string) data.
592
593 A derived class would typically provide an _encode_bin() or _encode_hex() method
594 for implementing this specifically for the given file. This function checks which
595 of the method exists, add calls them (with conversion, as needed).
596
597 Args:
598 abstract_data : dict representing the decoded data
599 Returns:
600 hex string encoded data
601 """
Harald Welteb2edd142021-01-08 23:29:35 +0100602 method = getattr(self, '_encode_hex', None)
603 if callable(method):
604 return method(abstract_data)
605 method = getattr(self, '_encode_bin', None)
606 if callable(method):
607 raw_bin_data = method(abstract_data)
608 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200609 if self._construct:
610 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200611 elif self._tlv:
612 self._tlv.from_dict(abstract_data)
613 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200614 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100615
616
617class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200618 """Linear Fixed EF (Entry File) in the smart card filesystem.
619
620 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
621 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100622
623 @with_default_category('Linear Fixed EF Commands')
624 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200625 """Shell commands specific for Linear Fixed EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100626 def __init__(self):
627 super().__init__()
628
629 read_rec_parser = argparse.ArgumentParser()
630 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100631 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 +0100632 @cmd2.with_argparser(read_rec_parser)
633 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100634 """Read one or multiple records from a record-oriented EF"""
635 for r in range(opts.count):
636 recnr = opts.record_nr + r
637 (data, sw) = self._cmd.rs.read_record(recnr)
638 if (len(data) > 0):
639 recstr = str(data)
640 else:
641 recstr = "(empty)"
642 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100643
644 read_rec_dec_parser = argparse.ArgumentParser()
645 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200646 read_rec_dec_parser.add_argument('--oneline', action='store_true',
647 help='No JSON pretty-printing, dump as a single line')
Harald Welteb2edd142021-01-08 23:29:35 +0100648 @cmd2.with_argparser(read_rec_dec_parser)
649 def do_read_record_decoded(self, opts):
650 """Read + decode a record from a record-oriented EF"""
651 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200652 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100653
Harald Welte850b72a2021-04-07 09:33:03 +0200654 read_recs_parser = argparse.ArgumentParser()
655 @cmd2.with_argparser(read_recs_parser)
656 def do_read_records(self, opts):
657 """Read all records from a record-oriented EF"""
658 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
659 for recnr in range(1, 1 + num_of_rec):
660 (data, sw) = self._cmd.rs.read_record(recnr)
661 if (len(data) > 0):
662 recstr = str(data)
663 else:
664 recstr = "(empty)"
665 self._cmd.poutput("%03d %s" % (recnr, recstr))
666
667 read_recs_dec_parser = argparse.ArgumentParser()
668 read_recs_dec_parser.add_argument('--oneline', action='store_true',
669 help='No JSON pretty-printing, dump as a single line')
670 @cmd2.with_argparser(read_recs_dec_parser)
671 def do_read_records_decoded(self, opts):
672 """Read + decode all records from a record-oriented EF"""
673 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
674 # collect all results in list so they are rendered as JSON list when printing
675 data_list = []
676 for recnr in range(1, 1 + num_of_rec):
677 (data, sw) = self._cmd.rs.read_record_dec(recnr)
678 data_list.append(data)
679 self._cmd.poutput_json(data_list, opts.oneline)
680
Harald Welteb2edd142021-01-08 23:29:35 +0100681 upd_rec_parser = argparse.ArgumentParser()
682 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
683 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
684 @cmd2.with_argparser(upd_rec_parser)
685 def do_update_record(self, opts):
686 """Update (write) data to a record-oriented EF"""
687 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100688 if data:
689 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100690
691 upd_rec_dec_parser = argparse.ArgumentParser()
692 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maierfe1fb032021-04-20 22:33:12 +0200693 upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200694 upd_rec_dec_parser.add_argument('--json-path', type=str,
695 help='JSON path to modify specific element of record only')
Harald Welteb2edd142021-01-08 23:29:35 +0100696 @cmd2.with_argparser(upd_rec_dec_parser)
697 def do_update_record_decoded(self, opts):
698 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200699 if opts.json_path:
700 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
701 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
702 else:
703 data_json = json.loads(opts.data)
704 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100705 if data:
706 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100707
Harald Welte4145d3c2021-04-08 20:34:13 +0200708 edit_rec_dec_parser = argparse.ArgumentParser()
709 edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
710 @cmd2.with_argparser(edit_rec_dec_parser)
711 def do_edit_record_decoded(self, opts):
712 """Edit the JSON representation of one record in an editor."""
713 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200714 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200715 filename = '%s/file' % dirname
716 # write existing data as JSON to file
717 with open(filename, 'w') as text_file:
718 json.dump(orig_json, text_file, indent=4)
719 # run a text editor
720 self._cmd._run_editor(filename)
721 with open(filename, 'r') as text_file:
722 edited_json = json.load(text_file)
723 if edited_json == orig_json:
724 self._cmd.poutput("Data not modified, skipping write")
725 else:
726 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
727 if data:
728 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200729
730
Harald Welteee3501f2021-04-02 13:00:18 +0200731 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
732 parent:Optional[CardDF]=None, rec_len={1,None}):
733 """
734 Args:
735 fid : File Identifier (4 hex digits)
736 sfid : Short File Identifier (2 hex digits, optional)
737 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200738 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200739 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200740 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200741 """
Harald Welteb2edd142021-01-08 23:29:35 +0100742 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
743 self.rec_len = rec_len
744 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200745 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200746 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100747
Harald Welteee3501f2021-04-02 13:00:18 +0200748 def decode_record_hex(self, raw_hex_data:str) -> dict:
749 """Decode raw (hex string) 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_hex_data : hex-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_hex', None)
761 if callable(method):
762 return method(raw_hex_data)
763 raw_bin_data = h2b(raw_hex_data)
764 method = getattr(self, '_decode_record_bin', None)
765 if callable(method):
766 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200767 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200768 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200769 elif self._tlv:
770 self._tlv.from_tlv(raw_bin_data)
771 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100772 return {'raw': raw_bin_data.hex()}
773
Harald Welteee3501f2021-04-02 13:00:18 +0200774 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
775 """Decode raw (binary) data into abstract representation.
776
777 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
778 method for implementing this specifically for the given file. This function checks which
779 of the method exists, add calls them (with conversion, as needed).
780
781 Args:
782 raw_bin_data : binary encoded data
783 Returns:
784 abstract_data; dict representing the decoded data
785 """
Harald Welteb2edd142021-01-08 23:29:35 +0100786 method = getattr(self, '_decode_record_bin', None)
787 if callable(method):
788 return method(raw_bin_data)
789 raw_hex_data = b2h(raw_bin_data)
790 method = getattr(self, '_decode_record_hex', None)
791 if callable(method):
792 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200793 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200794 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200795 elif self._tlv:
796 self._tlv.from_tlv(raw_bin_data)
797 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100798 return {'raw': raw_hex_data}
799
Harald Welteee3501f2021-04-02 13:00:18 +0200800 def encode_record_hex(self, abstract_data:dict) -> str:
801 """Encode abstract representation into raw (hex string) data.
802
803 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
804 method for implementing this specifically for the given file. This function checks which
805 of the method exists, add calls them (with conversion, as needed).
806
807 Args:
808 abstract_data : dict representing the decoded data
809 Returns:
810 hex string encoded data
811 """
Harald Welteb2edd142021-01-08 23:29:35 +0100812 method = getattr(self, '_encode_record_hex', None)
813 if callable(method):
814 return method(abstract_data)
815 method = getattr(self, '_encode_record_bin', None)
816 if callable(method):
817 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200818 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200819 if self._construct:
820 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200821 elif self._tlv:
822 self._tlv.from_dict(abstract_data)
823 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200824 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100825
Harald Welteee3501f2021-04-02 13:00:18 +0200826 def encode_record_bin(self, abstract_data:dict) -> bytearray:
827 """Encode abstract representation into raw (binary) data.
828
829 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
830 method for implementing this specifically for the given file. This function checks which
831 of the method exists, add calls them (with conversion, as needed).
832
833 Args:
834 abstract_data : dict representing the decoded data
835 Returns:
836 binary encoded data
837 """
Harald Welteb2edd142021-01-08 23:29:35 +0100838 method = getattr(self, '_encode_record_bin', None)
839 if callable(method):
840 return method(abstract_data)
841 method = getattr(self, '_encode_record_hex', None)
842 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200843 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200844 if self._construct:
845 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200846 elif self._tlv:
847 self._tlv.from_dict(abstract_data)
848 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200849 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100850
851class CyclicEF(LinFixedEF):
852 """Cyclic EF (Entry File) in the smart card filesystem"""
853 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Welteee3501f2021-04-02 13:00:18 +0200854 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
855 rec_len={1,None}):
Harald Welteb2edd142021-01-08 23:29:35 +0100856 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
857
858class TransRecEF(TransparentEF):
859 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200860
Harald Welteb2edd142021-01-08 23:29:35 +0100861 These are the real odd-balls and mostly look like mistakes in the specification:
862 Specified as 'transparent' EF, but actually containing several fixed-length records
863 inside.
864 We add a special class for those, so the user only has to provide encoder/decoder functions
865 for a record, while this class takes care of split / merge of records.
866 """
Harald Welte1e456572021-04-02 17:16:30 +0200867 def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
868 parent:Optional[CardDF]=None, size={1,None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200869 """
870 Args:
871 fid : File Identifier (4 hex digits)
872 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200873 name : Brief name of the file, like EF_ICCID
874 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200875 parent : Parent CardFile object within filesystem hierarchy
876 rec_len : Length of the fixed-length records within transparent EF
877 size : tuple of (minimum_size, recommended_size)
878 """
Harald Welteb2edd142021-01-08 23:29:35 +0100879 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
880 self.rec_len = rec_len
881
Harald Welteee3501f2021-04-02 13:00:18 +0200882 def decode_record_hex(self, raw_hex_data:str) -> dict:
883 """Decode raw (hex string) data into abstract representation.
884
885 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
886 method for implementing this specifically for the given file. This function checks which
887 of the method exists, add calls them (with conversion, as needed).
888
889 Args:
890 raw_hex_data : hex-encoded data
891 Returns:
892 abstract_data; dict representing the decoded data
893 """
Harald Welteb2edd142021-01-08 23:29:35 +0100894 method = getattr(self, '_decode_record_hex', None)
895 if callable(method):
896 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200897 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100898 method = getattr(self, '_decode_record_bin', None)
899 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +0100900 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200901 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200902 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200903 elif self._tlv:
904 self._tlv.from_tlv(raw_bin_data)
905 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100906 return {'raw': raw_hex_data}
907
Harald Welteee3501f2021-04-02 13:00:18 +0200908 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
909 """Decode raw (binary) data into abstract representation.
910
911 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
912 method for implementing this specifically for the given file. This function checks which
913 of the method exists, add calls them (with conversion, as needed).
914
915 Args:
916 raw_bin_data : binary encoded data
917 Returns:
918 abstract_data; dict representing the decoded data
919 """
Harald Welteb2edd142021-01-08 23:29:35 +0100920 method = getattr(self, '_decode_record_bin', None)
921 if callable(method):
922 return method(raw_bin_data)
923 raw_hex_data = b2h(raw_bin_data)
924 method = getattr(self, '_decode_record_hex', None)
925 if callable(method):
926 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200927 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200928 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200929 elif self._tlv:
930 self._tlv.from_tlv(raw_bin_data)
931 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100932 return {'raw': raw_hex_data}
933
Harald Welteee3501f2021-04-02 13:00:18 +0200934 def encode_record_hex(self, abstract_data:dict) -> str:
935 """Encode abstract representation into raw (hex string) data.
936
937 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
938 method for implementing this specifically for the given file. This function checks which
939 of the method exists, add calls them (with conversion, as needed).
940
941 Args:
942 abstract_data : dict representing the decoded data
943 Returns:
944 hex string encoded data
945 """
Harald Welteb2edd142021-01-08 23:29:35 +0100946 method = getattr(self, '_encode_record_hex', None)
947 if callable(method):
948 return method(abstract_data)
949 method = getattr(self, '_encode_record_bin', None)
950 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +0200951 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200952 if self._construct:
953 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +0200954 elif self._tlv:
955 self._tlv.from_dict(abstract_data)
956 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200957 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100958
Harald Welteee3501f2021-04-02 13:00:18 +0200959 def encode_record_bin(self, abstract_data:dict) -> bytearray:
960 """Encode abstract representation into raw (binary) data.
961
962 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
963 method for implementing this specifically for the given file. This function checks which
964 of the method exists, add calls them (with conversion, as needed).
965
966 Args:
967 abstract_data : dict representing the decoded data
968 Returns:
969 binary encoded data
970 """
Harald Welteb2edd142021-01-08 23:29:35 +0100971 method = getattr(self, '_encode_record_bin', None)
972 if callable(method):
973 return method(abstract_data)
974 method = getattr(self, '_encode_record_hex', None)
975 if callable(method):
976 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200977 if self._construct:
978 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200979 elif self._tlv:
980 self._tlv.from_dict(abstract_data)
981 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200982 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100983
Harald Welteee3501f2021-04-02 13:00:18 +0200984 def _decode_bin(self, raw_bin_data:bytearray):
Harald Welteb2edd142021-01-08 23:29:35 +0100985 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
986 return [self.decode_record_bin(x) for x in chunks]
987
Harald Welteee3501f2021-04-02 13:00:18 +0200988 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +0100989 chunks = [self.encode_record_bin(x) for x in abstract_data]
990 # FIXME: pad to file size
991 return b''.join(chunks)
992
993
Harald Welte917d98c2021-04-21 11:51:25 +0200994class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +0200995 """BER-TLV EF (Entry File) in the smart card filesystem.
996 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +0100997
Harald Welte27881622021-04-21 11:16:31 +0200998 NOTE: We currently don't really support those, this class is simply a wrapper
999 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1000 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001001
Harald Welte917d98c2021-04-21 11:51:25 +02001002 @with_default_category('BER-TLV EF Commands')
1003 class ShellCommands(CommandSet):
1004 """Shell commands specific for BER-TLV EFs."""
1005 def __init__(self):
1006 super().__init__()
1007
1008 retrieve_data_parser = argparse.ArgumentParser()
1009 retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1010 @cmd2.with_argparser(retrieve_data_parser)
1011 def do_retrieve_data(self, opts):
1012 """Retrieve (Read) data from a BER-TLV EF"""
1013 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1014 self._cmd.poutput(data)
1015
1016 def do_retrieve_tags(self, opts):
1017 """List tags available in a given BER-TLV EF"""
1018 tags = self._cmd.rs.retrieve_tags()
1019 self._cmd.poutput(tags)
1020
1021 set_data_parser = argparse.ArgumentParser()
1022 set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1023 set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
1024 @cmd2.with_argparser(set_data_parser)
1025 def do_set_data(self, opts):
1026 """Set (Write) data for a given tag in a BER-TLV EF"""
1027 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1028 if data:
1029 self._cmd.poutput(data)
1030
1031 del_data_parser = argparse.ArgumentParser()
1032 del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1033 @cmd2.with_argparser(del_data_parser)
1034 def do_delete_data(self, opts):
1035 """Delete data for a given tag in a BER-TLV EF"""
1036 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1037 if data:
1038 self._cmd.poutput(data)
1039
1040
1041 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
1042 size={1,None}):
1043 """
1044 Args:
1045 fid : File Identifier (4 hex digits)
1046 sfid : Short File Identifier (2 hex digits, optional)
1047 name : Brief name of the file, lik EF_ICCID
1048 desc : Description of the file
1049 parent : Parent CardFile object within filesystem hierarchy
1050 size : tuple of (minimum_size, recommended_size)
1051 """
1052 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
1053 self._construct = None
1054 self.size = size
1055 self.shell_commands = [self.ShellCommands()]
1056
Harald Welteb2edd142021-01-08 23:29:35 +01001057
1058class RuntimeState(object):
1059 """Represent the runtime state of a session with a card."""
Harald Welteee3501f2021-04-02 13:00:18 +02001060 def __init__(self, card, profile:'CardProfile'):
1061 """
1062 Args:
1063 card : pysim.cards.Card instance
1064 profile : CardProfile instance
1065 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001066 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001067 self.card = card
Harald Weltefe8a7442021-04-10 11:51:54 +02001068 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001069 self.profile = profile
Philipp Maier51cad0d2021-11-08 15:45:10 +01001070
1071 # make sure the class and selection control bytes, which are specified
1072 # by the card profile are used
1073 self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
1074
Harald Welte5ce35242021-04-02 20:27:05 +02001075 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001076 apps = self._match_applications()
1077 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001078 if a.adf:
1079 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001080 for f in self.profile.files_in_mf:
1081 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001082 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001083
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001084 # make sure that when the runtime state is created, the card is also
1085 # in a defined state.
1086 self.reset()
1087
Philipp Maier1e896f32021-03-10 17:02:53 +01001088 def _match_applications(self):
1089 """match the applications from the profile with applications on the card"""
1090 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001091
1092 # When the profile does not feature any applications, then we are done already
1093 if not apps_profile:
1094 return []
1095
1096 # Read AIDs from card and match them against the applications defined by the
1097 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001098 aids_card = self.card.read_aids()
1099 apps_taken = []
1100 if aids_card:
1101 aids_taken = []
1102 print("AIDs on card:")
1103 for a in aids_card:
1104 for f in apps_profile:
1105 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001106 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001107 aids_taken.append(a)
1108 apps_taken.append(f)
1109 aids_unknown = set(aids_card) - set(aids_taken)
1110 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001111 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001112 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001113 print("warning: EF.DIR seems to be empty!")
1114
1115 # Some card applications may not be registered in EF.DIR, we will actively
1116 # probe for those applications
1117 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001118 try:
1119 data, sw = self.card.select_adf_by_aid(f.aid)
1120 if sw == "9000":
1121 print(" %s: %s" % (f.name, f.aid))
1122 apps_taken.append(f)
1123 except SwMatchError:
1124 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001125 return apps_taken
1126
Harald Weltedaf2b392021-05-03 23:17:29 +02001127 def reset(self, cmd_app=None) -> Hexstr:
1128 """Perform physical card reset and obtain ATR.
1129 Args:
1130 cmd_app : Command Application State (for unregistering old file commands)
1131 """
Philipp Maier946226a2021-10-29 18:31:03 +02001132 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001133 # select MF to reset internal state and to verify card really works
1134 self.select('MF', cmd_app)
1135 return atr
1136
Harald Welteee3501f2021-04-02 13:00:18 +02001137 def get_cwd(self) -> CardDF:
1138 """Obtain the current working directory.
1139
1140 Returns:
1141 CardDF instance
1142 """
Harald Welteb2edd142021-01-08 23:29:35 +01001143 if isinstance(self.selected_file, CardDF):
1144 return self.selected_file
1145 else:
1146 return self.selected_file.parent
1147
Harald Welte5ce35242021-04-02 20:27:05 +02001148 def get_application_df(self) -> Optional[CardADF]:
1149 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001150
1151 Returns:
1152 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001153 # iterate upwards from selected file; check if any is an ADF
1154 node = self.selected_file
1155 while node.parent != node:
1156 if isinstance(node, CardADF):
1157 return node
1158 node = node.parent
1159 return None
1160
Harald Welteee3501f2021-04-02 13:00:18 +02001161 def interpret_sw(self, sw:str):
1162 """Interpret a given status word relative to the currently selected application
1163 or the underlying card profile.
1164
1165 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001166 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001167
1168 Returns:
1169 Tuple of two strings
1170 """
Harald Welte86fbd392021-04-02 22:13:09 +02001171 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001172 adf = self.get_application_df()
1173 if adf:
1174 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001175 # The application either comes with its own interpret_sw
1176 # method or we will use the interpret_sw method from the
1177 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001178 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001179 res = app.interpret_sw(sw)
1180 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001181
Harald Welteee3501f2021-04-02 13:00:18 +02001182 def probe_file(self, fid:str, cmd_app=None):
1183 """Blindly try to select a file and automatically add a matching file
1184 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001185 if not is_hex(fid, 4, 4):
1186 raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
1187
1188 try:
1189 (data, sw) = self.card._scc.select_file(fid)
1190 except SwMatchError as swm:
1191 k = self.interpret_sw(swm.sw_actual)
1192 if not k:
1193 raise(swm)
1194 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1195
1196 select_resp = self.selected_file.decode_select_response(data)
1197 if (select_resp['file_descriptor']['file_type'] == 'df'):
1198 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
1199 else:
1200 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec9cdce32021-04-11 10:28:28 +02001201 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001202 else:
Harald Weltec9cdce32021-04-11 10:28:28 +02001203 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001204
1205 self.selected_file.add_files([f])
1206 self.selected_file = f
1207 return select_resp
1208
Harald Welteee3501f2021-04-02 13:00:18 +02001209 def select(self, name:str, cmd_app=None):
1210 """Select a file (EF, DF, ADF, MF, ...).
1211
1212 Args:
1213 name : Name of file to select
1214 cmd_app : Command Application State (for unregistering old file commands)
1215 """
Harald Welteb2edd142021-01-08 23:29:35 +01001216 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001217 if is_hex(name):
1218 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001219
1220 # unregister commands of old file
1221 if cmd_app and self.selected_file.shell_commands:
1222 for c in self.selected_file.shell_commands:
1223 cmd_app.unregister_command_set(c)
1224
Harald Welteb2edd142021-01-08 23:29:35 +01001225 if name in sels:
1226 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001227 try:
1228 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001229 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001230 else:
1231 (data, sw) = self.card._scc.select_file(f.fid)
1232 self.selected_file = f
1233 except SwMatchError as swm:
1234 k = self.interpret_sw(swm.sw_actual)
1235 if not k:
1236 raise(swm)
1237 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001238 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001239 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001240 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001241 # store the decoded FCP for later reference
1242 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001243
1244 # register commands of new file
1245 if cmd_app and self.selected_file.shell_commands:
1246 for c in self.selected_file.shell_commands:
1247 cmd_app.register_command_set(c)
1248
1249 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001250
Harald Welte34b05d32021-05-25 22:03:13 +02001251 def status(self):
1252 """Request STATUS (current selected file FCP) from card."""
1253 (data, sw) = self.card._scc.status()
1254 return self.selected_file.decode_select_response(data)
1255
Harald Welte485692b2021-05-25 22:21:44 +02001256 def activate_file(self, name:str):
1257 """Request ACTIVATE FILE of specified file."""
1258 sels = self.selected_file.get_selectables()
1259 f = sels[name]
1260 data, sw = self.card._scc.activate_file(f.fid)
1261 return data, sw
1262
Harald Welteee3501f2021-04-02 13:00:18 +02001263 def read_binary(self, length:int=None, offset:int=0):
1264 """Read [part of] a transparent EF binary data.
1265
1266 Args:
1267 length : Amount of data to read (None: as much as possible)
1268 offset : Offset into the file from which to read 'length' bytes
1269 Returns:
1270 binary data read from the file
1271 """
Harald Welteb2edd142021-01-08 23:29:35 +01001272 if not isinstance(self.selected_file, TransparentEF):
1273 raise TypeError("Only works with TransparentEF")
1274 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1275
Harald Welte2d4a64b2021-04-03 09:01:24 +02001276 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001277 """Read [part of] a transparent EF binary data and decode it.
1278
1279 Args:
1280 length : Amount of data to read (None: as much as possible)
1281 offset : Offset into the file from which to read 'length' bytes
1282 Returns:
1283 abstract decode data read from the file
1284 """
Harald Welteb2edd142021-01-08 23:29:35 +01001285 (data, sw) = self.read_binary()
1286 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001287 return (dec_data, sw)
1288
Harald Welteee3501f2021-04-02 13:00:18 +02001289 def update_binary(self, data_hex:str, offset:int=0):
1290 """Update transparent EF binary data.
1291
1292 Args:
1293 data_hex : hex string of data to be written
1294 offset : Offset into the file from which to write 'data_hex'
1295 """
Harald Welteb2edd142021-01-08 23:29:35 +01001296 if not isinstance(self.selected_file, TransparentEF):
1297 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001298 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001299
Harald Welteee3501f2021-04-02 13:00:18 +02001300 def update_binary_dec(self, data:dict):
1301 """Update transparent EF from abstract data. Encodes the data to binary and
1302 then updates the EF with it.
1303
1304 Args:
1305 data : abstract data which is to be encoded and written
1306 """
Harald Welteb2edd142021-01-08 23:29:35 +01001307 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001308 return self.update_binary(data_hex)
1309
Harald Welteee3501f2021-04-02 13:00:18 +02001310 def read_record(self, rec_nr:int=0):
1311 """Read a record as binary data.
1312
1313 Args:
1314 rec_nr : Record number to read
1315 Returns:
1316 hex string of binary data contained in record
1317 """
Harald Welteb2edd142021-01-08 23:29:35 +01001318 if not isinstance(self.selected_file, LinFixedEF):
1319 raise TypeError("Only works with Linear Fixed EF")
1320 # returns a string of hex nibbles
1321 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1322
Harald Welteee3501f2021-04-02 13:00:18 +02001323 def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
1324 """Read a record and decode it to abstract data.
1325
1326 Args:
1327 rec_nr : Record number to read
1328 Returns:
1329 abstract data contained in record
1330 """
Harald Welteb2edd142021-01-08 23:29:35 +01001331 (data, sw) = self.read_record(rec_nr)
1332 return (self.selected_file.decode_record_hex(data), sw)
1333
Harald Welteee3501f2021-04-02 13:00:18 +02001334 def update_record(self, rec_nr:int, data_hex:str):
1335 """Update a record with given binary data
1336
1337 Args:
1338 rec_nr : Record number to read
1339 data_hex : Hex string binary data to be written
1340 """
Harald Welteb2edd142021-01-08 23:29:35 +01001341 if not isinstance(self.selected_file, LinFixedEF):
1342 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001343 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 +01001344
Harald Welteee3501f2021-04-02 13:00:18 +02001345 def update_record_dec(self, rec_nr:int, data:dict):
1346 """Update a record with given abstract data. Will encode abstract to binary data
1347 and then write it to the given record on the card.
1348
1349 Args:
1350 rec_nr : Record number to read
1351 data_hex : Abstract data to be written
1352 """
Harald Welte1e456572021-04-02 17:16:30 +02001353 data_hex = self.selected_file.encode_record_hex(data)
1354 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001355
Harald Welte917d98c2021-04-21 11:51:25 +02001356 def retrieve_data(self, tag:int=0):
1357 """Read a DO/TLV as binary data.
1358
1359 Args:
1360 tag : Tag of TLV/DO to read
1361 Returns:
1362 hex string of full BER-TLV DO including Tag and Length
1363 """
1364 if not isinstance(self.selected_file, BerTlvEF):
1365 raise TypeError("Only works with BER-TLV EF")
1366 # returns a string of hex nibbles
1367 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1368
1369 def retrieve_tags(self):
1370 """Retrieve tags available on BER-TLV EF.
1371
1372 Returns:
1373 list of integer tags contained in EF
1374 """
1375 if not isinstance(self.selected_file, BerTlvEF):
1376 raise TypeError("Only works with BER-TLV EF")
1377 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001378 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001379 return list(value)
1380
1381 def set_data(self, tag:int, data_hex:str):
1382 """Update a TLV/DO with given binary data
1383
1384 Args:
1385 tag : Tag of TLV/DO to be written
1386 data_hex : Hex string binary data to be written (value portion)
1387 """
1388 if not isinstance(self.selected_file, BerTlvEF):
1389 raise TypeError("Only works with BER-TLV EF")
1390 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1391
Philipp Maier5d698e52021-09-16 13:18:01 +02001392 def unregister_cmds(self, cmd_app=None):
1393 """Unregister all file specific commands."""
1394 if cmd_app and self.selected_file.shell_commands:
1395 for c in self.selected_file.shell_commands:
1396 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001397
Harald Welteb2edd142021-01-08 23:29:35 +01001398
1399
1400class FileData(object):
1401 """Represent the runtime, on-card data."""
1402 def __init__(self, fdesc):
1403 self.desc = fdesc
1404 self.fcp = None
1405
1406
Harald Welteee3501f2021-04-02 13:00:18 +02001407def interpret_sw(sw_data:dict, sw:str):
1408 """Interpret a given status word.
1409
1410 Args:
1411 sw_data : Hierarchical dict of status word matches
1412 sw : status word to match (string of 4 hex digits)
1413 Returns:
1414 tuple of two strings (class_string, description)
1415 """
Harald Welteb2edd142021-01-08 23:29:35 +01001416 for class_str, swdict in sw_data.items():
1417 # first try direct match
1418 if sw in swdict:
1419 return (class_str, swdict[sw])
1420 # next try wildcard matches
1421 for pattern, descr in swdict.items():
1422 if sw_match(sw, pattern):
1423 return (class_str, descr)
1424 return None
1425
1426class CardApplication(object):
1427 """A card application is represented by an ADF (with contained hierarchy) and optionally
1428 some SW definitions."""
Harald Welte5ce35242021-04-02 20:27:05 +02001429 def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001430 """
1431 Args:
1432 adf : ADF name
1433 sw : Dict of status word conversions
1434 """
Harald Welteb2edd142021-01-08 23:29:35 +01001435 self.name = name
1436 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001437 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001438 # back-reference from ADF to Applicaiton
1439 if self.adf:
1440 self.aid = aid or self.adf.aid
1441 self.adf.application = self
1442 else:
1443 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001444
1445 def __str__(self):
1446 return "APP(%s)" % (self.name)
1447
1448 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001449 """Interpret a given status word within the application.
1450
1451 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001452 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001453
1454 Returns:
1455 Tuple of two strings
1456 """
Harald Welteb2edd142021-01-08 23:29:35 +01001457 return interpret_sw(self.sw, sw)
1458
Harald Weltef44256c2021-10-14 15:53:39 +02001459
1460class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001461 """A specific card model, typically having some additional vendor-specific files. All
1462 you need to do is to define a sub-class with a list of ATRs or an overridden match
1463 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001464 _atrs = []
1465
1466 @classmethod
1467 @abc.abstractmethod
1468 def add_files(cls, rs:RuntimeState):
1469 """Add model specific files to given RuntimeState."""
1470
1471 @classmethod
1472 def match(cls, scc:SimCardCommands) -> bool:
1473 """Test if given card matches this model."""
1474 card_atr = scc.get_atr()
1475 for atr in cls._atrs:
1476 atr_bin = toBytes(atr)
1477 if atr_bin == card_atr:
1478 print("Detected CardModel:", cls.__name__)
1479 return True
1480 return False
1481
1482 @staticmethod
1483 def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001484 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1485 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1486 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001487 for m in CardModel.__subclasses__():
1488 if m.match(scc):
1489 m.add_files(rs)