blob: 7100b21d14caf771a42a97f1871c44999c47e1a1 [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:
Harald Welte944cd2f2022-01-21 16:01:29 +0100535 t = self._tlv()
536 t.from_tlv(raw_bin_data)
537 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100538 return {'raw': raw_bin_data.hex()}
539
Harald Welteee3501f2021-04-02 13:00:18 +0200540 def decode_hex(self, raw_hex_data:str) -> dict:
541 """Decode raw (hex string) data into abstract representation.
542
543 A derived class would typically provide a _decode_bin() or _decode_hex() method
544 for implementing this specifically for the given file. This function checks which
545 of the method exists, add calls them (with conversion, as needed).
546
547 Args:
548 raw_hex_data : hex-encoded data
549 Returns:
550 abstract_data; dict representing the decoded data
551 """
Harald Welteb2edd142021-01-08 23:29:35 +0100552 method = getattr(self, '_decode_hex', None)
553 if callable(method):
554 return method(raw_hex_data)
555 raw_bin_data = h2b(raw_hex_data)
556 method = getattr(self, '_decode_bin', None)
557 if callable(method):
558 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200559 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200560 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200561 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100562 t = self._tlv()
563 t.from_tlv(raw_bin_data)
564 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100565 return {'raw': raw_bin_data.hex()}
566
Harald Welteee3501f2021-04-02 13:00:18 +0200567 def encode_bin(self, abstract_data:dict) -> bytearray:
568 """Encode abstract representation into raw (binary) data.
569
570 A derived class would typically provide an _encode_bin() or _encode_hex() method
571 for implementing this specifically for the given file. This function checks which
572 of the method exists, add calls them (with conversion, as needed).
573
574 Args:
575 abstract_data : dict representing the decoded data
576 Returns:
577 binary encoded data
578 """
Harald Welteb2edd142021-01-08 23:29:35 +0100579 method = getattr(self, '_encode_bin', None)
580 if callable(method):
581 return method(abstract_data)
582 method = getattr(self, '_encode_hex', None)
583 if callable(method):
584 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200585 if self._construct:
586 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200587 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100588 t = self._tlv()
589 t.from_dict(abstract_data)
590 return t.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200591 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100592
Harald Welteee3501f2021-04-02 13:00:18 +0200593 def encode_hex(self, abstract_data:dict) -> str:
594 """Encode abstract representation into raw (hex string) data.
595
596 A derived class would typically provide an _encode_bin() or _encode_hex() method
597 for implementing this specifically for the given file. This function checks which
598 of the method exists, add calls them (with conversion, as needed).
599
600 Args:
601 abstract_data : dict representing the decoded data
602 Returns:
603 hex string encoded data
604 """
Harald Welteb2edd142021-01-08 23:29:35 +0100605 method = getattr(self, '_encode_hex', None)
606 if callable(method):
607 return method(abstract_data)
608 method = getattr(self, '_encode_bin', None)
609 if callable(method):
610 raw_bin_data = method(abstract_data)
611 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200612 if self._construct:
613 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200614 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100615 t = self._tlv()
616 t.from_dict(abstract_data)
617 return b2h(t.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200618 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100619
620
621class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200622 """Linear Fixed EF (Entry File) in the smart card filesystem.
623
624 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
625 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100626
627 @with_default_category('Linear Fixed EF Commands')
628 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200629 """Shell commands specific for Linear Fixed EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100630 def __init__(self):
631 super().__init__()
632
633 read_rec_parser = argparse.ArgumentParser()
634 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100635 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 +0100636 @cmd2.with_argparser(read_rec_parser)
637 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100638 """Read one or multiple records from a record-oriented EF"""
639 for r in range(opts.count):
640 recnr = opts.record_nr + r
641 (data, sw) = self._cmd.rs.read_record(recnr)
642 if (len(data) > 0):
643 recstr = str(data)
644 else:
645 recstr = "(empty)"
646 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100647
648 read_rec_dec_parser = argparse.ArgumentParser()
649 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200650 read_rec_dec_parser.add_argument('--oneline', action='store_true',
651 help='No JSON pretty-printing, dump as a single line')
Harald Welteb2edd142021-01-08 23:29:35 +0100652 @cmd2.with_argparser(read_rec_dec_parser)
653 def do_read_record_decoded(self, opts):
654 """Read + decode a record from a record-oriented EF"""
655 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200656 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100657
Harald Welte850b72a2021-04-07 09:33:03 +0200658 read_recs_parser = argparse.ArgumentParser()
659 @cmd2.with_argparser(read_recs_parser)
660 def do_read_records(self, opts):
661 """Read all records from a record-oriented EF"""
662 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
663 for recnr in range(1, 1 + num_of_rec):
664 (data, sw) = self._cmd.rs.read_record(recnr)
665 if (len(data) > 0):
666 recstr = str(data)
667 else:
668 recstr = "(empty)"
669 self._cmd.poutput("%03d %s" % (recnr, recstr))
670
671 read_recs_dec_parser = argparse.ArgumentParser()
672 read_recs_dec_parser.add_argument('--oneline', action='store_true',
673 help='No JSON pretty-printing, dump as a single line')
674 @cmd2.with_argparser(read_recs_dec_parser)
675 def do_read_records_decoded(self, opts):
676 """Read + decode all records from a record-oriented EF"""
677 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
678 # collect all results in list so they are rendered as JSON list when printing
679 data_list = []
680 for recnr in range(1, 1 + num_of_rec):
681 (data, sw) = self._cmd.rs.read_record_dec(recnr)
682 data_list.append(data)
683 self._cmd.poutput_json(data_list, opts.oneline)
684
Harald Welteb2edd142021-01-08 23:29:35 +0100685 upd_rec_parser = argparse.ArgumentParser()
686 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
687 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
688 @cmd2.with_argparser(upd_rec_parser)
689 def do_update_record(self, opts):
690 """Update (write) data to a record-oriented EF"""
691 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100692 if data:
693 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100694
695 upd_rec_dec_parser = argparse.ArgumentParser()
696 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maierfe1fb032021-04-20 22:33:12 +0200697 upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200698 upd_rec_dec_parser.add_argument('--json-path', type=str,
699 help='JSON path to modify specific element of record only')
Harald Welteb2edd142021-01-08 23:29:35 +0100700 @cmd2.with_argparser(upd_rec_dec_parser)
701 def do_update_record_decoded(self, opts):
702 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200703 if opts.json_path:
704 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
705 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
706 else:
707 data_json = json.loads(opts.data)
708 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100709 if data:
710 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100711
Harald Welte4145d3c2021-04-08 20:34:13 +0200712 edit_rec_dec_parser = argparse.ArgumentParser()
713 edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
714 @cmd2.with_argparser(edit_rec_dec_parser)
715 def do_edit_record_decoded(self, opts):
716 """Edit the JSON representation of one record in an editor."""
717 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200718 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200719 filename = '%s/file' % dirname
720 # write existing data as JSON to file
721 with open(filename, 'w') as text_file:
722 json.dump(orig_json, text_file, indent=4)
723 # run a text editor
724 self._cmd._run_editor(filename)
725 with open(filename, 'r') as text_file:
726 edited_json = json.load(text_file)
727 if edited_json == orig_json:
728 self._cmd.poutput("Data not modified, skipping write")
729 else:
730 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
731 if data:
732 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200733
734
Harald Welteee3501f2021-04-02 13:00:18 +0200735 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
736 parent:Optional[CardDF]=None, rec_len={1,None}):
737 """
738 Args:
739 fid : File Identifier (4 hex digits)
740 sfid : Short File Identifier (2 hex digits, optional)
741 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200742 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200743 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200744 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200745 """
Harald Welteb2edd142021-01-08 23:29:35 +0100746 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
747 self.rec_len = rec_len
748 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200749 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200750 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100751
Harald Welteee3501f2021-04-02 13:00:18 +0200752 def decode_record_hex(self, raw_hex_data:str) -> dict:
753 """Decode raw (hex string) data into abstract representation.
754
755 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
756 method for implementing this specifically for the given file. This function checks which
757 of the method exists, add calls them (with conversion, as needed).
758
759 Args:
760 raw_hex_data : hex-encoded data
761 Returns:
762 abstract_data; dict representing the decoded data
763 """
Harald Welteb2edd142021-01-08 23:29:35 +0100764 method = getattr(self, '_decode_record_hex', None)
765 if callable(method):
766 return method(raw_hex_data)
767 raw_bin_data = h2b(raw_hex_data)
768 method = getattr(self, '_decode_record_bin', None)
769 if callable(method):
770 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200771 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200772 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200773 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100774 t = self._tlv()
775 t.from_tlv(raw_bin_data)
776 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100777 return {'raw': raw_bin_data.hex()}
778
Harald Welteee3501f2021-04-02 13:00:18 +0200779 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
780 """Decode raw (binary) data into abstract representation.
781
782 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
783 method for implementing this specifically for the given file. This function checks which
784 of the method exists, add calls them (with conversion, as needed).
785
786 Args:
787 raw_bin_data : binary encoded data
788 Returns:
789 abstract_data; dict representing the decoded data
790 """
Harald Welteb2edd142021-01-08 23:29:35 +0100791 method = getattr(self, '_decode_record_bin', None)
792 if callable(method):
793 return method(raw_bin_data)
794 raw_hex_data = b2h(raw_bin_data)
795 method = getattr(self, '_decode_record_hex', None)
796 if callable(method):
797 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200798 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200799 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200800 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100801 t = self._tlv()
802 t.from_tlv(raw_bin_data)
803 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100804 return {'raw': raw_hex_data}
805
Harald Welteee3501f2021-04-02 13:00:18 +0200806 def encode_record_hex(self, abstract_data:dict) -> str:
807 """Encode abstract representation into raw (hex string) data.
808
809 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
810 method for implementing this specifically for the given file. This function checks which
811 of the method exists, add calls them (with conversion, as needed).
812
813 Args:
814 abstract_data : dict representing the decoded data
815 Returns:
816 hex string encoded data
817 """
Harald Welteb2edd142021-01-08 23:29:35 +0100818 method = getattr(self, '_encode_record_hex', None)
819 if callable(method):
820 return method(abstract_data)
821 method = getattr(self, '_encode_record_bin', None)
822 if callable(method):
823 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200824 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200825 if self._construct:
826 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200827 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100828 t = self._tlv()
829 t.from_dict(abstract_data)
830 return b2h(t.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200831 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100832
Harald Welteee3501f2021-04-02 13:00:18 +0200833 def encode_record_bin(self, abstract_data:dict) -> bytearray:
834 """Encode abstract representation into raw (binary) data.
835
836 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
837 method for implementing this specifically for the given file. This function checks which
838 of the method exists, add calls them (with conversion, as needed).
839
840 Args:
841 abstract_data : dict representing the decoded data
842 Returns:
843 binary encoded data
844 """
Harald Welteb2edd142021-01-08 23:29:35 +0100845 method = getattr(self, '_encode_record_bin', None)
846 if callable(method):
847 return method(abstract_data)
848 method = getattr(self, '_encode_record_hex', None)
849 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200850 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200851 if self._construct:
852 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200853 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100854 t = self._tlv()
855 t.from_dict(abstract_data)
856 return t.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200857 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100858
859class CyclicEF(LinFixedEF):
860 """Cyclic EF (Entry File) in the smart card filesystem"""
861 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Welteee3501f2021-04-02 13:00:18 +0200862 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
863 rec_len={1,None}):
Harald Welteb2edd142021-01-08 23:29:35 +0100864 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
865
866class TransRecEF(TransparentEF):
867 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200868
Harald Welteb2edd142021-01-08 23:29:35 +0100869 These are the real odd-balls and mostly look like mistakes in the specification:
870 Specified as 'transparent' EF, but actually containing several fixed-length records
871 inside.
872 We add a special class for those, so the user only has to provide encoder/decoder functions
873 for a record, while this class takes care of split / merge of records.
874 """
Harald Welte1e456572021-04-02 17:16:30 +0200875 def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
876 parent:Optional[CardDF]=None, size={1,None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200877 """
878 Args:
879 fid : File Identifier (4 hex digits)
880 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200881 name : Brief name of the file, like EF_ICCID
882 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200883 parent : Parent CardFile object within filesystem hierarchy
884 rec_len : Length of the fixed-length records within transparent EF
885 size : tuple of (minimum_size, recommended_size)
886 """
Harald Welteb2edd142021-01-08 23:29:35 +0100887 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
888 self.rec_len = rec_len
889
Harald Welteee3501f2021-04-02 13:00:18 +0200890 def decode_record_hex(self, raw_hex_data:str) -> dict:
891 """Decode raw (hex string) data into abstract representation.
892
893 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
894 method for implementing this specifically for the given file. This function checks which
895 of the method exists, add calls them (with conversion, as needed).
896
897 Args:
898 raw_hex_data : hex-encoded data
899 Returns:
900 abstract_data; dict representing the decoded data
901 """
Harald Welteb2edd142021-01-08 23:29:35 +0100902 method = getattr(self, '_decode_record_hex', None)
903 if callable(method):
904 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200905 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100906 method = getattr(self, '_decode_record_bin', None)
907 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +0100908 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200909 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200910 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200911 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100912 t = self._tlv()
913 t.from_tlv(raw_bin_data)
914 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100915 return {'raw': raw_hex_data}
916
Harald Welteee3501f2021-04-02 13:00:18 +0200917 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
918 """Decode raw (binary) data into abstract representation.
919
920 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
921 method for implementing this specifically for the given file. This function checks which
922 of the method exists, add calls them (with conversion, as needed).
923
924 Args:
925 raw_bin_data : binary encoded data
926 Returns:
927 abstract_data; dict representing the decoded data
928 """
Harald Welteb2edd142021-01-08 23:29:35 +0100929 method = getattr(self, '_decode_record_bin', None)
930 if callable(method):
931 return method(raw_bin_data)
932 raw_hex_data = b2h(raw_bin_data)
933 method = getattr(self, '_decode_record_hex', None)
934 if callable(method):
935 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200936 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200937 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200938 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100939 t = self._tlv()
940 t.from_tlv(raw_bin_data)
941 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100942 return {'raw': raw_hex_data}
943
Harald Welteee3501f2021-04-02 13:00:18 +0200944 def encode_record_hex(self, abstract_data:dict) -> str:
945 """Encode abstract representation into raw (hex string) data.
946
947 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
948 method for implementing this specifically for the given file. This function checks which
949 of the method exists, add calls them (with conversion, as needed).
950
951 Args:
952 abstract_data : dict representing the decoded data
953 Returns:
954 hex string encoded data
955 """
Harald Welteb2edd142021-01-08 23:29:35 +0100956 method = getattr(self, '_encode_record_hex', None)
957 if callable(method):
958 return method(abstract_data)
959 method = getattr(self, '_encode_record_bin', None)
960 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +0200961 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200962 if self._construct:
963 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +0200964 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100965 t = self._tlv()
966 t.from_dict(abstract_data)
967 return b2h(t.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200968 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100969
Harald Welteee3501f2021-04-02 13:00:18 +0200970 def encode_record_bin(self, abstract_data:dict) -> bytearray:
971 """Encode abstract representation into raw (binary) data.
972
973 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
974 method for implementing this specifically for the given file. This function checks which
975 of the method exists, add calls them (with conversion, as needed).
976
977 Args:
978 abstract_data : dict representing the decoded data
979 Returns:
980 binary encoded data
981 """
Harald Welteb2edd142021-01-08 23:29:35 +0100982 method = getattr(self, '_encode_record_bin', None)
983 if callable(method):
984 return method(abstract_data)
985 method = getattr(self, '_encode_record_hex', None)
986 if callable(method):
987 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200988 if self._construct:
989 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200990 elif self._tlv:
Harald Welte944cd2f2022-01-21 16:01:29 +0100991 t = self._tlv()
992 t.from_dict(abstract_data)
993 return t.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200994 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100995
Harald Welteee3501f2021-04-02 13:00:18 +0200996 def _decode_bin(self, raw_bin_data:bytearray):
Harald Welteb2edd142021-01-08 23:29:35 +0100997 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
998 return [self.decode_record_bin(x) for x in chunks]
999
Harald Welteee3501f2021-04-02 13:00:18 +02001000 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001001 chunks = [self.encode_record_bin(x) for x in abstract_data]
1002 # FIXME: pad to file size
1003 return b''.join(chunks)
1004
1005
Harald Welte917d98c2021-04-21 11:51:25 +02001006class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001007 """BER-TLV EF (Entry File) in the smart card filesystem.
1008 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001009
Harald Welte27881622021-04-21 11:16:31 +02001010 NOTE: We currently don't really support those, this class is simply a wrapper
1011 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1012 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001013
Harald Welte917d98c2021-04-21 11:51:25 +02001014 @with_default_category('BER-TLV EF Commands')
1015 class ShellCommands(CommandSet):
1016 """Shell commands specific for BER-TLV EFs."""
1017 def __init__(self):
1018 super().__init__()
1019
1020 retrieve_data_parser = argparse.ArgumentParser()
1021 retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1022 @cmd2.with_argparser(retrieve_data_parser)
1023 def do_retrieve_data(self, opts):
1024 """Retrieve (Read) data from a BER-TLV EF"""
1025 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1026 self._cmd.poutput(data)
1027
1028 def do_retrieve_tags(self, opts):
1029 """List tags available in a given BER-TLV EF"""
1030 tags = self._cmd.rs.retrieve_tags()
1031 self._cmd.poutput(tags)
1032
1033 set_data_parser = argparse.ArgumentParser()
1034 set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1035 set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
1036 @cmd2.with_argparser(set_data_parser)
1037 def do_set_data(self, opts):
1038 """Set (Write) data for a given tag in a BER-TLV EF"""
1039 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1040 if data:
1041 self._cmd.poutput(data)
1042
1043 del_data_parser = argparse.ArgumentParser()
1044 del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1045 @cmd2.with_argparser(del_data_parser)
1046 def do_delete_data(self, opts):
1047 """Delete data for a given tag in a BER-TLV EF"""
1048 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1049 if data:
1050 self._cmd.poutput(data)
1051
1052
1053 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
1054 size={1,None}):
1055 """
1056 Args:
1057 fid : File Identifier (4 hex digits)
1058 sfid : Short File Identifier (2 hex digits, optional)
1059 name : Brief name of the file, lik EF_ICCID
1060 desc : Description of the file
1061 parent : Parent CardFile object within filesystem hierarchy
1062 size : tuple of (minimum_size, recommended_size)
1063 """
1064 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
1065 self._construct = None
1066 self.size = size
1067 self.shell_commands = [self.ShellCommands()]
1068
Harald Welteb2edd142021-01-08 23:29:35 +01001069
1070class RuntimeState(object):
1071 """Represent the runtime state of a session with a card."""
Harald Welteee3501f2021-04-02 13:00:18 +02001072 def __init__(self, card, profile:'CardProfile'):
1073 """
1074 Args:
1075 card : pysim.cards.Card instance
1076 profile : CardProfile instance
1077 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001078 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001079 self.card = card
Harald Weltefe8a7442021-04-10 11:51:54 +02001080 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001081 self.profile = profile
Philipp Maier51cad0d2021-11-08 15:45:10 +01001082
1083 # make sure the class and selection control bytes, which are specified
1084 # by the card profile are used
1085 self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
1086
Harald Welte5ce35242021-04-02 20:27:05 +02001087 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001088 apps = self._match_applications()
1089 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001090 if a.adf:
1091 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001092 for f in self.profile.files_in_mf:
1093 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001094 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001095
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001096 # make sure that when the runtime state is created, the card is also
1097 # in a defined state.
1098 self.reset()
1099
Philipp Maier1e896f32021-03-10 17:02:53 +01001100 def _match_applications(self):
1101 """match the applications from the profile with applications on the card"""
1102 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001103
1104 # When the profile does not feature any applications, then we are done already
1105 if not apps_profile:
1106 return []
1107
1108 # Read AIDs from card and match them against the applications defined by the
1109 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001110 aids_card = self.card.read_aids()
1111 apps_taken = []
1112 if aids_card:
1113 aids_taken = []
1114 print("AIDs on card:")
1115 for a in aids_card:
1116 for f in apps_profile:
1117 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001118 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001119 aids_taken.append(a)
1120 apps_taken.append(f)
1121 aids_unknown = set(aids_card) - set(aids_taken)
1122 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001123 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001124 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001125 print("warning: EF.DIR seems to be empty!")
1126
1127 # Some card applications may not be registered in EF.DIR, we will actively
1128 # probe for those applications
1129 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001130 try:
1131 data, sw = self.card.select_adf_by_aid(f.aid)
1132 if sw == "9000":
1133 print(" %s: %s" % (f.name, f.aid))
1134 apps_taken.append(f)
1135 except SwMatchError:
1136 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001137 return apps_taken
1138
Harald Weltedaf2b392021-05-03 23:17:29 +02001139 def reset(self, cmd_app=None) -> Hexstr:
1140 """Perform physical card reset and obtain ATR.
1141 Args:
1142 cmd_app : Command Application State (for unregistering old file commands)
1143 """
Philipp Maier946226a2021-10-29 18:31:03 +02001144 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001145 # select MF to reset internal state and to verify card really works
1146 self.select('MF', cmd_app)
1147 return atr
1148
Harald Welteee3501f2021-04-02 13:00:18 +02001149 def get_cwd(self) -> CardDF:
1150 """Obtain the current working directory.
1151
1152 Returns:
1153 CardDF instance
1154 """
Harald Welteb2edd142021-01-08 23:29:35 +01001155 if isinstance(self.selected_file, CardDF):
1156 return self.selected_file
1157 else:
1158 return self.selected_file.parent
1159
Harald Welte5ce35242021-04-02 20:27:05 +02001160 def get_application_df(self) -> Optional[CardADF]:
1161 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001162
1163 Returns:
1164 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001165 # iterate upwards from selected file; check if any is an ADF
1166 node = self.selected_file
1167 while node.parent != node:
1168 if isinstance(node, CardADF):
1169 return node
1170 node = node.parent
1171 return None
1172
Harald Welteee3501f2021-04-02 13:00:18 +02001173 def interpret_sw(self, sw:str):
1174 """Interpret a given status word relative to the currently selected application
1175 or the underlying card profile.
1176
1177 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001178 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001179
1180 Returns:
1181 Tuple of two strings
1182 """
Harald Welte86fbd392021-04-02 22:13:09 +02001183 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001184 adf = self.get_application_df()
1185 if adf:
1186 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001187 # The application either comes with its own interpret_sw
1188 # method or we will use the interpret_sw method from the
1189 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001190 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001191 res = app.interpret_sw(sw)
1192 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001193
Harald Welteee3501f2021-04-02 13:00:18 +02001194 def probe_file(self, fid:str, cmd_app=None):
1195 """Blindly try to select a file and automatically add a matching file
1196 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001197 if not is_hex(fid, 4, 4):
1198 raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
1199
1200 try:
1201 (data, sw) = self.card._scc.select_file(fid)
1202 except SwMatchError as swm:
1203 k = self.interpret_sw(swm.sw_actual)
1204 if not k:
1205 raise(swm)
1206 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1207
1208 select_resp = self.selected_file.decode_select_response(data)
1209 if (select_resp['file_descriptor']['file_type'] == 'df'):
1210 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
1211 else:
1212 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec9cdce32021-04-11 10:28:28 +02001213 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 +01001214 else:
Harald Weltec9cdce32021-04-11 10:28:28 +02001215 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 +01001216
1217 self.selected_file.add_files([f])
1218 self.selected_file = f
1219 return select_resp
1220
Harald Welteee3501f2021-04-02 13:00:18 +02001221 def select(self, name:str, cmd_app=None):
1222 """Select a file (EF, DF, ADF, MF, ...).
1223
1224 Args:
1225 name : Name of file to select
1226 cmd_app : Command Application State (for unregistering old file commands)
1227 """
Harald Welteb2edd142021-01-08 23:29:35 +01001228 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001229 if is_hex(name):
1230 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001231
1232 # unregister commands of old file
1233 if cmd_app and self.selected_file.shell_commands:
1234 for c in self.selected_file.shell_commands:
1235 cmd_app.unregister_command_set(c)
1236
Harald Welteb2edd142021-01-08 23:29:35 +01001237 if name in sels:
1238 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001239 try:
1240 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001241 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001242 else:
1243 (data, sw) = self.card._scc.select_file(f.fid)
1244 self.selected_file = f
1245 except SwMatchError as swm:
1246 k = self.interpret_sw(swm.sw_actual)
1247 if not k:
1248 raise(swm)
1249 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001250 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001251 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001252 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001253 # store the decoded FCP for later reference
1254 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001255
1256 # register commands of new file
1257 if cmd_app and self.selected_file.shell_commands:
1258 for c in self.selected_file.shell_commands:
1259 cmd_app.register_command_set(c)
1260
1261 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001262
Harald Welte34b05d32021-05-25 22:03:13 +02001263 def status(self):
1264 """Request STATUS (current selected file FCP) from card."""
1265 (data, sw) = self.card._scc.status()
1266 return self.selected_file.decode_select_response(data)
1267
Harald Welte485692b2021-05-25 22:21:44 +02001268 def activate_file(self, name:str):
1269 """Request ACTIVATE FILE of specified file."""
1270 sels = self.selected_file.get_selectables()
1271 f = sels[name]
1272 data, sw = self.card._scc.activate_file(f.fid)
1273 return data, sw
1274
Harald Welteee3501f2021-04-02 13:00:18 +02001275 def read_binary(self, length:int=None, offset:int=0):
1276 """Read [part of] a transparent EF binary data.
1277
1278 Args:
1279 length : Amount of data to read (None: as much as possible)
1280 offset : Offset into the file from which to read 'length' bytes
1281 Returns:
1282 binary data read from the file
1283 """
Harald Welteb2edd142021-01-08 23:29:35 +01001284 if not isinstance(self.selected_file, TransparentEF):
1285 raise TypeError("Only works with TransparentEF")
1286 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1287
Harald Welte2d4a64b2021-04-03 09:01:24 +02001288 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001289 """Read [part of] a transparent EF binary data and decode it.
1290
1291 Args:
1292 length : Amount of data to read (None: as much as possible)
1293 offset : Offset into the file from which to read 'length' bytes
1294 Returns:
1295 abstract decode data read from the file
1296 """
Harald Welteb2edd142021-01-08 23:29:35 +01001297 (data, sw) = self.read_binary()
1298 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001299 return (dec_data, sw)
1300
Harald Welteee3501f2021-04-02 13:00:18 +02001301 def update_binary(self, data_hex:str, offset:int=0):
1302 """Update transparent EF binary data.
1303
1304 Args:
1305 data_hex : hex string of data to be written
1306 offset : Offset into the file from which to write 'data_hex'
1307 """
Harald Welteb2edd142021-01-08 23:29:35 +01001308 if not isinstance(self.selected_file, TransparentEF):
1309 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001310 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001311
Harald Welteee3501f2021-04-02 13:00:18 +02001312 def update_binary_dec(self, data:dict):
1313 """Update transparent EF from abstract data. Encodes the data to binary and
1314 then updates the EF with it.
1315
1316 Args:
1317 data : abstract data which is to be encoded and written
1318 """
Harald Welteb2edd142021-01-08 23:29:35 +01001319 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001320 return self.update_binary(data_hex)
1321
Harald Welteee3501f2021-04-02 13:00:18 +02001322 def read_record(self, rec_nr:int=0):
1323 """Read a record as binary data.
1324
1325 Args:
1326 rec_nr : Record number to read
1327 Returns:
1328 hex string of binary data contained in record
1329 """
Harald Welteb2edd142021-01-08 23:29:35 +01001330 if not isinstance(self.selected_file, LinFixedEF):
1331 raise TypeError("Only works with Linear Fixed EF")
1332 # returns a string of hex nibbles
1333 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1334
Harald Welteee3501f2021-04-02 13:00:18 +02001335 def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
1336 """Read a record and decode it to abstract data.
1337
1338 Args:
1339 rec_nr : Record number to read
1340 Returns:
1341 abstract data contained in record
1342 """
Harald Welteb2edd142021-01-08 23:29:35 +01001343 (data, sw) = self.read_record(rec_nr)
1344 return (self.selected_file.decode_record_hex(data), sw)
1345
Harald Welteee3501f2021-04-02 13:00:18 +02001346 def update_record(self, rec_nr:int, data_hex:str):
1347 """Update a record with given binary data
1348
1349 Args:
1350 rec_nr : Record number to read
1351 data_hex : Hex string binary data to be written
1352 """
Harald Welteb2edd142021-01-08 23:29:35 +01001353 if not isinstance(self.selected_file, LinFixedEF):
1354 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001355 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 +01001356
Harald Welteee3501f2021-04-02 13:00:18 +02001357 def update_record_dec(self, rec_nr:int, data:dict):
1358 """Update a record with given abstract data. Will encode abstract to binary data
1359 and then write it to the given record on the card.
1360
1361 Args:
1362 rec_nr : Record number to read
1363 data_hex : Abstract data to be written
1364 """
Harald Welte1e456572021-04-02 17:16:30 +02001365 data_hex = self.selected_file.encode_record_hex(data)
1366 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001367
Harald Welte917d98c2021-04-21 11:51:25 +02001368 def retrieve_data(self, tag:int=0):
1369 """Read a DO/TLV as binary data.
1370
1371 Args:
1372 tag : Tag of TLV/DO to read
1373 Returns:
1374 hex string of full BER-TLV DO including Tag and Length
1375 """
1376 if not isinstance(self.selected_file, BerTlvEF):
1377 raise TypeError("Only works with BER-TLV EF")
1378 # returns a string of hex nibbles
1379 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1380
1381 def retrieve_tags(self):
1382 """Retrieve tags available on BER-TLV EF.
1383
1384 Returns:
1385 list of integer tags contained in EF
1386 """
1387 if not isinstance(self.selected_file, BerTlvEF):
1388 raise TypeError("Only works with BER-TLV EF")
1389 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001390 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001391 return list(value)
1392
1393 def set_data(self, tag:int, data_hex:str):
1394 """Update a TLV/DO with given binary data
1395
1396 Args:
1397 tag : Tag of TLV/DO to be written
1398 data_hex : Hex string binary data to be written (value portion)
1399 """
1400 if not isinstance(self.selected_file, BerTlvEF):
1401 raise TypeError("Only works with BER-TLV EF")
1402 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1403
Philipp Maier5d698e52021-09-16 13:18:01 +02001404 def unregister_cmds(self, cmd_app=None):
1405 """Unregister all file specific commands."""
1406 if cmd_app and self.selected_file.shell_commands:
1407 for c in self.selected_file.shell_commands:
1408 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001409
Harald Welteb2edd142021-01-08 23:29:35 +01001410
1411
1412class FileData(object):
1413 """Represent the runtime, on-card data."""
1414 def __init__(self, fdesc):
1415 self.desc = fdesc
1416 self.fcp = None
1417
1418
Harald Welteee3501f2021-04-02 13:00:18 +02001419def interpret_sw(sw_data:dict, sw:str):
1420 """Interpret a given status word.
1421
1422 Args:
1423 sw_data : Hierarchical dict of status word matches
1424 sw : status word to match (string of 4 hex digits)
1425 Returns:
1426 tuple of two strings (class_string, description)
1427 """
Harald Welteb2edd142021-01-08 23:29:35 +01001428 for class_str, swdict in sw_data.items():
1429 # first try direct match
1430 if sw in swdict:
1431 return (class_str, swdict[sw])
1432 # next try wildcard matches
1433 for pattern, descr in swdict.items():
1434 if sw_match(sw, pattern):
1435 return (class_str, descr)
1436 return None
1437
1438class CardApplication(object):
1439 """A card application is represented by an ADF (with contained hierarchy) and optionally
1440 some SW definitions."""
Harald Welte5ce35242021-04-02 20:27:05 +02001441 def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001442 """
1443 Args:
1444 adf : ADF name
1445 sw : Dict of status word conversions
1446 """
Harald Welteb2edd142021-01-08 23:29:35 +01001447 self.name = name
1448 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001449 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001450 # back-reference from ADF to Applicaiton
1451 if self.adf:
1452 self.aid = aid or self.adf.aid
1453 self.adf.application = self
1454 else:
1455 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001456
1457 def __str__(self):
1458 return "APP(%s)" % (self.name)
1459
1460 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001461 """Interpret a given status word within the application.
1462
1463 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001464 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001465
1466 Returns:
1467 Tuple of two strings
1468 """
Harald Welteb2edd142021-01-08 23:29:35 +01001469 return interpret_sw(self.sw, sw)
1470
Harald Weltef44256c2021-10-14 15:53:39 +02001471
1472class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001473 """A specific card model, typically having some additional vendor-specific files. All
1474 you need to do is to define a sub-class with a list of ATRs or an overridden match
1475 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001476 _atrs = []
1477
1478 @classmethod
1479 @abc.abstractmethod
1480 def add_files(cls, rs:RuntimeState):
1481 """Add model specific files to given RuntimeState."""
1482
1483 @classmethod
1484 def match(cls, scc:SimCardCommands) -> bool:
1485 """Test if given card matches this model."""
1486 card_atr = scc.get_atr()
1487 for atr in cls._atrs:
1488 atr_bin = toBytes(atr)
1489 if atr_bin == card_atr:
1490 print("Detected CardModel:", cls.__name__)
1491 return True
1492 return False
1493
1494 @staticmethod
1495 def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001496 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1497 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1498 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001499 for m in CardModel.__subclasses__():
1500 if m.match(scc):
1501 m.add_files(rs)