blob: 32688db0787811007d4b1c080171a84fe2bb9781 [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 Welte1e456572021-04-02 17:16:30 +0200175 return list(sels.keys())
Harald Welteb2edd142021-01-08 23:29:35 +0100176
Harald Welteee3501f2021-04-02 13:00:18 +0200177 def decode_select_response(self, data_hex:str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100178 """Decode the response to a SELECT command.
179
180 Args:
181 data_hex: Hex string of the select response
182 """
183
184 # When the current file does not implement a custom select response decoder,
185 # we just ask the parent file to decode the select response. If this method
186 # is not overloaded by the current file we will again ask the parent file.
187 # This way we recursively travel up the file system tree until we hit a file
188 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200189 if self.parent:
190 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100191
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100192 def get_profile(self):
193 """Get the profile associated with this file. If this file does not have any
194 profile assigned, try to find a file above (usually the MF) in the filesystem
195 hirarchy that has a profile assigned
196 """
197
198 # If we have a profile set, return it
199 if self.profile:
200 return self.profile
201
202 # Walk up recursively until we hit a parent that has a profile set
203 if self.parent:
204 return self.parent.get_profile()
205 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100206
207class CardDF(CardFile):
208 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100209
210 @with_default_category('DF/ADF Commands')
211 class ShellCommands(CommandSet):
212 def __init__(self):
213 super().__init__()
214
Harald Welteb2edd142021-01-08 23:29:35 +0100215 def __init__(self, **kwargs):
216 if not isinstance(self, CardADF):
217 if not 'fid' in kwargs:
218 raise TypeError('fid is mandatory for all DF')
219 super().__init__(**kwargs)
220 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100221 self.shell_commands = [self.ShellCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +0100222
223 def __str__(self):
224 return "DF(%s)" % (super().__str__())
225
Harald Welteee3501f2021-04-02 13:00:18 +0200226 def add_file(self, child:CardFile, ignore_existing:bool=False):
227 """Add a child (DF/EF) to this DF.
228 Args:
229 child: The new DF/EF to be added
230 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
231 """
Harald Welteb2edd142021-01-08 23:29:35 +0100232 if not isinstance(child, CardFile):
233 raise TypeError("Expected a File instance")
Philipp Maier3aec8712021-03-09 21:49:01 +0100234 if not is_hex(child.fid, minlen = 4, maxlen = 4):
235 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100236 if child.name in CardFile.RESERVED_NAMES:
237 raise ValueError("File name %s is a reserved name" % (child.name))
238 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100239 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100240 if child.fid in self.children:
241 if ignore_existing:
242 return
Harald Welte977035c2021-04-21 11:01:26 +0200243 raise ValueError("File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100244 if self.lookup_file_by_sfid(child.sfid):
Harald Welte977035c2021-04-21 11:01:26 +0200245 raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100246 if self.lookup_file_by_name(child.name):
247 if ignore_existing:
248 return
Harald Welte977035c2021-04-21 11:01:26 +0200249 raise ValueError("File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100250 self.children[child.fid] = child
251 child.parent = self
252
Harald Welteee3501f2021-04-02 13:00:18 +0200253 def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False):
254 """Add a list of child (DF/EF) to this DF
255
256 Args:
257 children: List of new DF/EFs to be added
258 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
259 """
Harald Welteb2edd142021-01-08 23:29:35 +0100260 for child in children:
261 self.add_file(child, ignore_existing)
262
Harald Welteee3501f2021-04-02 13:00:18 +0200263 def get_selectables(self, flags = []) -> dict:
264 """Return a dict of {'identifier': File} that is selectable from the current DF.
265
266 Args:
267 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
268 If not specified, all selectables will be returned.
269 Returns:
270 dict containing all selectable items. Key is identifier (string), value
271 a reference to a CardFile (or derived class) instance.
272 """
Harald Welteb2edd142021-01-08 23:29:35 +0100273 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100274 sels = super().get_selectables(flags)
275 if flags == [] or 'FIDS' in flags:
276 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100277 if flags == [] or 'FNAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100278 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100279 return sels
280
Harald Welte1e456572021-04-02 17:16:30 +0200281 def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200282 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100283 if name == None:
284 return None
285 for i in self.children.values():
286 if i.name and i.name == name:
287 return i
288 return None
289
Harald Welte1e456572021-04-02 17:16:30 +0200290 def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200291 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100292 if sfid == None:
293 return None
294 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200295 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100296 return i
297 return None
298
Harald Welteee3501f2021-04-02 13:00:18 +0200299 def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]:
300 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100301 if fid in self.children:
302 return self.children[fid]
303 return None
304
305
306class CardMF(CardDF):
307 """MF (Master File) in the smart card filesystem"""
308 def __init__(self, **kwargs):
309 # can be overridden; use setdefault
310 kwargs.setdefault('fid', '3f00')
311 kwargs.setdefault('name', 'MF')
312 kwargs.setdefault('desc', 'Master File (directory root)')
313 # cannot be overridden; use assignment
314 kwargs['parent'] = self
315 super().__init__(**kwargs)
316 self.applications = dict()
317
318 def __str__(self):
319 return "MF(%s)" % (self.fid)
320
Harald Welte5ce35242021-04-02 20:27:05 +0200321 def add_application_df(self, app:'CardADF'):
322 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100323 if not isinstance(app, CardADF):
324 raise TypeError("Expected an ADF instance")
325 if app.aid in self.applications:
326 raise ValueError("AID %s already exists" % (app.aid))
327 self.applications[app.aid] = app
328 app.parent=self
329
330 def get_app_names(self):
331 """Get list of completions (AID names)"""
332 return [x.name for x in self.applications]
333
Harald Welteee3501f2021-04-02 13:00:18 +0200334 def get_selectables(self, flags = []) -> dict:
335 """Return a dict of {'identifier': File} that is selectable from the current DF.
336
337 Args:
338 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
339 If not specified, all selectables will be returned.
340 Returns:
341 dict containing all selectable items. Key is identifier (string), value
342 a reference to a CardFile (or derived class) instance.
343 """
Philipp Maier786f7812021-02-25 16:48:10 +0100344 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100345 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100346 return sels
347
Harald Welteee3501f2021-04-02 13:00:18 +0200348 def get_app_selectables(self, flags = []) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100349 """Get applications by AID + name"""
350 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100351 if flags == [] or 'AIDS' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100352 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100353 if flags == [] or 'ANAMES' in flags:
Philipp Maier786f7812021-02-25 16:48:10 +0100354 sels.update({x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100355 return sels
356
Philipp Maier9e42e7f2021-11-16 15:46:42 +0100357 def decode_select_response(self, data_hex:str) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200358 """Decode the response to a SELECT command.
359
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100360 This is the fall-back method which automatically defers to the standard decoding
361 method defined by the card profile. When no profile is set, then no decoding is
362 performed. Specific derived classes (usually ADF) can overload this method to
363 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200364 """
Harald Welteb2edd142021-01-08 23:29:35 +0100365
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100366 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100367
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100368 if profile:
369 return profile.decode_select_response(data_hex)
370 else:
371 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100372
373class CardADF(CardDF):
374 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Welteee3501f2021-04-02 13:00:18 +0200375 def __init__(self, aid:str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100376 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200377 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200378 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100379 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200380 mf = self.get_mf()
381 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200382 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100383
384 def __str__(self):
385 return "ADF(%s)" % (self.aid)
386
Harald Welteee3501f2021-04-02 13:00:18 +0200387 def _path_element(self, prefer_name:bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100388 if self.name and prefer_name:
389 return self.name
390 else:
391 return self.aid
392
393
394class CardEF(CardFile):
395 """EF (Entry File) in the smart card filesystem"""
396 def __init__(self, *, fid, **kwargs):
397 kwargs['fid'] = fid
398 super().__init__(**kwargs)
399
400 def __str__(self):
401 return "EF(%s)" % (super().__str__())
402
Harald Welteee3501f2021-04-02 13:00:18 +0200403 def get_selectables(self, flags = []) -> dict:
404 """Return a dict of {'identifier': File} that is selectable from the current DF.
405
406 Args:
407 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
408 If not specified, all selectables will be returned.
409 Returns:
410 dict containing all selectable items. Key is identifier (string), value
411 a reference to a CardFile (or derived class) instance.
412 """
Harald Welteb2edd142021-01-08 23:29:35 +0100413 #global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100414 sels = super().get_selectables(flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100415 sels.update({x.name:x for x in self.parent.children.values() if x != self})
416 return sels
417
418
419class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200420 """Transparent EF (Entry File) in the smart card filesystem.
421
422 A Transparent EF is a binary file with no formal structure. This is contrary to
423 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100424
425 @with_default_category('Transparent EF Commands')
426 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200427 """Shell commands specific for transparent EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100428 def __init__(self):
429 super().__init__()
430
431 read_bin_parser = argparse.ArgumentParser()
432 read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
433 read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read')
434 @cmd2.with_argparser(read_bin_parser)
435 def do_read_binary(self, opts):
436 """Read binary data from a transparent EF"""
437 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
438 self._cmd.poutput(data)
439
Harald Weltebcad86c2021-04-06 20:08:39 +0200440 read_bin_dec_parser = argparse.ArgumentParser()
441 read_bin_dec_parser.add_argument('--oneline', action='store_true',
442 help='No JSON pretty-printing, dump as a single line')
443 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100444 def do_read_binary_decoded(self, opts):
445 """Read + decode data from a transparent EF"""
446 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200447 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100448
449 upd_bin_parser = argparse.ArgumentParser()
450 upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read')
451 upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write')
452 @cmd2.with_argparser(upd_bin_parser)
453 def do_update_binary(self, opts):
454 """Update (Write) data of a transparent EF"""
455 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100456 if data:
457 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100458
459 upd_bin_dec_parser = argparse.ArgumentParser()
460 upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200461 upd_bin_dec_parser.add_argument('--json-path', type=str,
462 help='JSON path to modify specific element of file only')
Harald Welteb2edd142021-01-08 23:29:35 +0100463 @cmd2.with_argparser(upd_bin_dec_parser)
464 def do_update_binary_decoded(self, opts):
465 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200466 if opts.json_path:
467 (data_json, sw) = self._cmd.rs.read_binary_dec()
468 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
469 else:
470 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100471 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100472 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200473 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100474
Harald Welte4145d3c2021-04-08 20:34:13 +0200475 def do_edit_binary_decoded(self, opts):
476 """Edit the JSON representation of the EF contents in an editor."""
477 (orig_json, sw) = self._cmd.rs.read_binary_dec()
478 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
479 filename = '%s/file' % dirname
480 # write existing data as JSON to file
481 with open(filename, 'w') as text_file:
482 json.dump(orig_json, text_file, indent=4)
483 # run a text editor
484 self._cmd._run_editor(filename)
485 with open(filename, 'r') as text_file:
486 edited_json = json.load(text_file)
487 if edited_json == orig_json:
488 self._cmd.poutput("Data not modified, skipping write")
489 else:
490 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
491 if data:
492 self._cmd.poutput_json(data)
493
494
Harald Welteee3501f2021-04-02 13:00:18 +0200495 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
496 size={1,None}):
497 """
498 Args:
499 fid : File Identifier (4 hex digits)
500 sfid : Short File Identifier (2 hex digits, optional)
501 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200502 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200503 parent : Parent CardFile object within filesystem hierarchy
504 size : tuple of (minimum_size, recommended_size)
505 """
Harald Welteb2edd142021-01-08 23:29:35 +0100506 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200507 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200508 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100509 self.size = size
510 self.shell_commands = [self.ShellCommands()]
511
Harald Welteee3501f2021-04-02 13:00:18 +0200512 def decode_bin(self, raw_bin_data:bytearray) -> dict:
513 """Decode raw (binary) data into abstract representation.
514
515 A derived class would typically provide a _decode_bin() or _decode_hex() method
516 for implementing this specifically for the given file. This function checks which
517 of the method exists, add calls them (with conversion, as needed).
518
519 Args:
520 raw_bin_data : binary encoded data
521 Returns:
522 abstract_data; dict representing the decoded data
523 """
Harald Welteb2edd142021-01-08 23:29:35 +0100524 method = getattr(self, '_decode_bin', None)
525 if callable(method):
526 return method(raw_bin_data)
527 method = getattr(self, '_decode_hex', None)
528 if callable(method):
529 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200530 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200531 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200532 elif self._tlv:
533 self._tlv.from_tlv(raw_bin_data)
534 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100535 return {'raw': raw_bin_data.hex()}
536
Harald Welteee3501f2021-04-02 13:00:18 +0200537 def decode_hex(self, raw_hex_data:str) -> dict:
538 """Decode raw (hex string) data into abstract representation.
539
540 A derived class would typically provide a _decode_bin() or _decode_hex() method
541 for implementing this specifically for the given file. This function checks which
542 of the method exists, add calls them (with conversion, as needed).
543
544 Args:
545 raw_hex_data : hex-encoded data
546 Returns:
547 abstract_data; dict representing the decoded data
548 """
Harald Welteb2edd142021-01-08 23:29:35 +0100549 method = getattr(self, '_decode_hex', None)
550 if callable(method):
551 return method(raw_hex_data)
552 raw_bin_data = h2b(raw_hex_data)
553 method = getattr(self, '_decode_bin', None)
554 if callable(method):
555 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200556 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200557 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200558 elif self._tlv:
559 self._tlv.from_tlv(raw_bin_data)
560 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100561 return {'raw': raw_bin_data.hex()}
562
Harald Welteee3501f2021-04-02 13:00:18 +0200563 def encode_bin(self, abstract_data:dict) -> bytearray:
564 """Encode abstract representation into raw (binary) data.
565
566 A derived class would typically provide an _encode_bin() or _encode_hex() method
567 for implementing this specifically for the given file. This function checks which
568 of the method exists, add calls them (with conversion, as needed).
569
570 Args:
571 abstract_data : dict representing the decoded data
572 Returns:
573 binary encoded data
574 """
Harald Welteb2edd142021-01-08 23:29:35 +0100575 method = getattr(self, '_encode_bin', None)
576 if callable(method):
577 return method(abstract_data)
578 method = getattr(self, '_encode_hex', None)
579 if callable(method):
580 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200581 if self._construct:
582 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200583 elif self._tlv:
584 self._tlv.from_dict(abstract_data)
585 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200586 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100587
Harald Welteee3501f2021-04-02 13:00:18 +0200588 def encode_hex(self, abstract_data:dict) -> str:
589 """Encode abstract representation into raw (hex string) data.
590
591 A derived class would typically provide an _encode_bin() or _encode_hex() method
592 for implementing this specifically for the given file. This function checks which
593 of the method exists, add calls them (with conversion, as needed).
594
595 Args:
596 abstract_data : dict representing the decoded data
597 Returns:
598 hex string encoded data
599 """
Harald Welteb2edd142021-01-08 23:29:35 +0100600 method = getattr(self, '_encode_hex', None)
601 if callable(method):
602 return method(abstract_data)
603 method = getattr(self, '_encode_bin', None)
604 if callable(method):
605 raw_bin_data = method(abstract_data)
606 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200607 if self._construct:
608 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200609 elif self._tlv:
610 self._tlv.from_dict(abstract_data)
611 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200612 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100613
614
615class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200616 """Linear Fixed EF (Entry File) in the smart card filesystem.
617
618 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
619 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100620
621 @with_default_category('Linear Fixed EF Commands')
622 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200623 """Shell commands specific for Linear Fixed EFs."""
Harald Welteb2edd142021-01-08 23:29:35 +0100624 def __init__(self):
625 super().__init__()
626
627 read_rec_parser = argparse.ArgumentParser()
628 read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maier41555732021-02-25 16:52:08 +0100629 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 +0100630 @cmd2.with_argparser(read_rec_parser)
631 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100632 """Read one or multiple records from a record-oriented EF"""
633 for r in range(opts.count):
634 recnr = opts.record_nr + r
635 (data, sw) = self._cmd.rs.read_record(recnr)
636 if (len(data) > 0):
637 recstr = str(data)
638 else:
639 recstr = "(empty)"
640 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100641
642 read_rec_dec_parser = argparse.ArgumentParser()
643 read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200644 read_rec_dec_parser.add_argument('--oneline', action='store_true',
645 help='No JSON pretty-printing, dump as a single line')
Harald Welteb2edd142021-01-08 23:29:35 +0100646 @cmd2.with_argparser(read_rec_dec_parser)
647 def do_read_record_decoded(self, opts):
648 """Read + decode a record from a record-oriented EF"""
649 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200650 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100651
Harald Welte850b72a2021-04-07 09:33:03 +0200652 read_recs_parser = argparse.ArgumentParser()
653 @cmd2.with_argparser(read_recs_parser)
654 def do_read_records(self, opts):
655 """Read all records from a record-oriented EF"""
656 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
657 for recnr in range(1, 1 + num_of_rec):
658 (data, sw) = self._cmd.rs.read_record(recnr)
659 if (len(data) > 0):
660 recstr = str(data)
661 else:
662 recstr = "(empty)"
663 self._cmd.poutput("%03d %s" % (recnr, recstr))
664
665 read_recs_dec_parser = argparse.ArgumentParser()
666 read_recs_dec_parser.add_argument('--oneline', action='store_true',
667 help='No JSON pretty-printing, dump as a single line')
668 @cmd2.with_argparser(read_recs_dec_parser)
669 def do_read_records_decoded(self, opts):
670 """Read + decode all records from a record-oriented EF"""
671 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
672 # collect all results in list so they are rendered as JSON list when printing
673 data_list = []
674 for recnr in range(1, 1 + num_of_rec):
675 (data, sw) = self._cmd.rs.read_record_dec(recnr)
676 data_list.append(data)
677 self._cmd.poutput_json(data_list, opts.oneline)
678
Harald Welteb2edd142021-01-08 23:29:35 +0100679 upd_rec_parser = argparse.ArgumentParser()
680 upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
681 upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write')
682 @cmd2.with_argparser(upd_rec_parser)
683 def do_update_record(self, opts):
684 """Update (write) data to a record-oriented EF"""
685 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100686 if data:
687 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100688
689 upd_rec_dec_parser = argparse.ArgumentParser()
690 upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read')
Philipp Maierfe1fb032021-04-20 22:33:12 +0200691 upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200692 upd_rec_dec_parser.add_argument('--json-path', type=str,
693 help='JSON path to modify specific element of record only')
Harald Welteb2edd142021-01-08 23:29:35 +0100694 @cmd2.with_argparser(upd_rec_dec_parser)
695 def do_update_record_decoded(self, opts):
696 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200697 if opts.json_path:
698 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
699 js_path_modify(data_json, opts.json_path, json.loads(opts.data))
700 else:
701 data_json = json.loads(opts.data)
702 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100703 if data:
704 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100705
Harald Welte4145d3c2021-04-08 20:34:13 +0200706 edit_rec_dec_parser = argparse.ArgumentParser()
707 edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited')
708 @cmd2.with_argparser(edit_rec_dec_parser)
709 def do_edit_record_decoded(self, opts):
710 """Edit the JSON representation of one record in an editor."""
711 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200712 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200713 filename = '%s/file' % dirname
714 # write existing data as JSON to file
715 with open(filename, 'w') as text_file:
716 json.dump(orig_json, text_file, indent=4)
717 # run a text editor
718 self._cmd._run_editor(filename)
719 with open(filename, 'r') as text_file:
720 edited_json = json.load(text_file)
721 if edited_json == orig_json:
722 self._cmd.poutput("Data not modified, skipping write")
723 else:
724 (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json)
725 if data:
726 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200727
728
Harald Welteee3501f2021-04-02 13:00:18 +0200729 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None,
730 parent:Optional[CardDF]=None, rec_len={1,None}):
731 """
732 Args:
733 fid : File Identifier (4 hex digits)
734 sfid : Short File Identifier (2 hex digits, optional)
735 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200736 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200737 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200738 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200739 """
Harald Welteb2edd142021-01-08 23:29:35 +0100740 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
741 self.rec_len = rec_len
742 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200743 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200744 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100745
Harald Welteee3501f2021-04-02 13:00:18 +0200746 def decode_record_hex(self, raw_hex_data:str) -> dict:
747 """Decode raw (hex string) data into abstract representation.
748
749 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
750 method for implementing this specifically for the given file. This function checks which
751 of the method exists, add calls them (with conversion, as needed).
752
753 Args:
754 raw_hex_data : hex-encoded data
755 Returns:
756 abstract_data; dict representing the decoded data
757 """
Harald Welteb2edd142021-01-08 23:29:35 +0100758 method = getattr(self, '_decode_record_hex', None)
759 if callable(method):
760 return method(raw_hex_data)
761 raw_bin_data = h2b(raw_hex_data)
762 method = getattr(self, '_decode_record_bin', None)
763 if callable(method):
764 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200765 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200766 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200767 elif self._tlv:
768 self._tlv.from_tlv(raw_bin_data)
769 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100770 return {'raw': raw_bin_data.hex()}
771
Harald Welteee3501f2021-04-02 13:00:18 +0200772 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
773 """Decode raw (binary) data into abstract representation.
774
775 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
776 method for implementing this specifically for the given file. This function checks which
777 of the method exists, add calls them (with conversion, as needed).
778
779 Args:
780 raw_bin_data : binary encoded data
781 Returns:
782 abstract_data; dict representing the decoded data
783 """
Harald Welteb2edd142021-01-08 23:29:35 +0100784 method = getattr(self, '_decode_record_bin', None)
785 if callable(method):
786 return method(raw_bin_data)
787 raw_hex_data = b2h(raw_bin_data)
788 method = getattr(self, '_decode_record_hex', None)
789 if callable(method):
790 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200791 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200792 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200793 elif self._tlv:
794 self._tlv.from_tlv(raw_bin_data)
795 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100796 return {'raw': raw_hex_data}
797
Harald Welteee3501f2021-04-02 13:00:18 +0200798 def encode_record_hex(self, abstract_data:dict) -> str:
799 """Encode abstract representation into raw (hex string) data.
800
801 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
802 method for implementing this specifically for the given file. This function checks which
803 of the method exists, add calls them (with conversion, as needed).
804
805 Args:
806 abstract_data : dict representing the decoded data
807 Returns:
808 hex string encoded data
809 """
Harald Welteb2edd142021-01-08 23:29:35 +0100810 method = getattr(self, '_encode_record_hex', None)
811 if callable(method):
812 return method(abstract_data)
813 method = getattr(self, '_encode_record_bin', None)
814 if callable(method):
815 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200816 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200817 if self._construct:
818 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200819 elif self._tlv:
820 self._tlv.from_dict(abstract_data)
821 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200822 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100823
Harald Welteee3501f2021-04-02 13:00:18 +0200824 def encode_record_bin(self, abstract_data:dict) -> bytearray:
825 """Encode abstract representation into raw (binary) data.
826
827 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
828 method for implementing this specifically for the given file. This function checks which
829 of the method exists, add calls them (with conversion, as needed).
830
831 Args:
832 abstract_data : dict representing the decoded data
833 Returns:
834 binary encoded data
835 """
Harald Welteb2edd142021-01-08 23:29:35 +0100836 method = getattr(self, '_encode_record_bin', None)
837 if callable(method):
838 return method(abstract_data)
839 method = getattr(self, '_encode_record_hex', None)
840 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200841 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200842 if self._construct:
843 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200844 elif self._tlv:
845 self._tlv.from_dict(abstract_data)
846 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200847 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100848
849class CyclicEF(LinFixedEF):
850 """Cyclic EF (Entry File) in the smart card filesystem"""
851 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Welteee3501f2021-04-02 13:00:18 +0200852 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
853 rec_len={1,None}):
Harald Welteb2edd142021-01-08 23:29:35 +0100854 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len)
855
856class TransRecEF(TransparentEF):
857 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200858
Harald Welteb2edd142021-01-08 23:29:35 +0100859 These are the real odd-balls and mostly look like mistakes in the specification:
860 Specified as 'transparent' EF, but actually containing several fixed-length records
861 inside.
862 We add a special class for those, so the user only has to provide encoder/decoder functions
863 for a record, while this class takes care of split / merge of records.
864 """
Harald Welte1e456572021-04-02 17:16:30 +0200865 def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
866 parent:Optional[CardDF]=None, size={1,None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200867 """
868 Args:
869 fid : File Identifier (4 hex digits)
870 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200871 name : Brief name of the file, like EF_ICCID
872 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200873 parent : Parent CardFile object within filesystem hierarchy
874 rec_len : Length of the fixed-length records within transparent EF
875 size : tuple of (minimum_size, recommended_size)
876 """
Harald Welteb2edd142021-01-08 23:29:35 +0100877 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
878 self.rec_len = rec_len
879
Harald Welteee3501f2021-04-02 13:00:18 +0200880 def decode_record_hex(self, raw_hex_data:str) -> dict:
881 """Decode raw (hex string) data into abstract representation.
882
883 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
884 method for implementing this specifically for the given file. This function checks which
885 of the method exists, add calls them (with conversion, as needed).
886
887 Args:
888 raw_hex_data : hex-encoded data
889 Returns:
890 abstract_data; dict representing the decoded data
891 """
Harald Welteb2edd142021-01-08 23:29:35 +0100892 method = getattr(self, '_decode_record_hex', None)
893 if callable(method):
894 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200895 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100896 method = getattr(self, '_decode_record_bin', None)
897 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +0100898 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200899 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200900 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200901 elif self._tlv:
902 self._tlv.from_tlv(raw_bin_data)
903 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100904 return {'raw': raw_hex_data}
905
Harald Welteee3501f2021-04-02 13:00:18 +0200906 def decode_record_bin(self, raw_bin_data:bytearray) -> dict:
907 """Decode raw (binary) data into abstract representation.
908
909 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
910 method for implementing this specifically for the given file. This function checks which
911 of the method exists, add calls them (with conversion, as needed).
912
913 Args:
914 raw_bin_data : binary encoded data
915 Returns:
916 abstract_data; dict representing the decoded data
917 """
Harald Welteb2edd142021-01-08 23:29:35 +0100918 method = getattr(self, '_decode_record_bin', None)
919 if callable(method):
920 return method(raw_bin_data)
921 raw_hex_data = b2h(raw_bin_data)
922 method = getattr(self, '_decode_record_hex', None)
923 if callable(method):
924 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200925 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200926 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200927 elif self._tlv:
928 self._tlv.from_tlv(raw_bin_data)
929 return self._tlv.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100930 return {'raw': raw_hex_data}
931
Harald Welteee3501f2021-04-02 13:00:18 +0200932 def encode_record_hex(self, abstract_data:dict) -> str:
933 """Encode abstract representation into raw (hex string) data.
934
935 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
936 method for implementing this specifically for the given file. This function checks which
937 of the method exists, add calls them (with conversion, as needed).
938
939 Args:
940 abstract_data : dict representing the decoded data
941 Returns:
942 hex string encoded data
943 """
Harald Welteb2edd142021-01-08 23:29:35 +0100944 method = getattr(self, '_encode_record_hex', None)
945 if callable(method):
946 return method(abstract_data)
947 method = getattr(self, '_encode_record_bin', None)
948 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +0200949 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200950 if self._construct:
951 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +0200952 elif self._tlv:
953 self._tlv.from_dict(abstract_data)
954 return b2h(self._tlv.to_tlv())
Harald Welte1aae4a02021-10-14 16:12:42 +0200955 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100956
Harald Welteee3501f2021-04-02 13:00:18 +0200957 def encode_record_bin(self, abstract_data:dict) -> bytearray:
958 """Encode abstract representation into raw (binary) data.
959
960 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
961 method for implementing this specifically for the given file. This function checks which
962 of the method exists, add calls them (with conversion, as needed).
963
964 Args:
965 abstract_data : dict representing the decoded data
966 Returns:
967 binary encoded data
968 """
Harald Welteb2edd142021-01-08 23:29:35 +0100969 method = getattr(self, '_encode_record_bin', None)
970 if callable(method):
971 return method(abstract_data)
972 method = getattr(self, '_encode_record_hex', None)
973 if callable(method):
974 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200975 if self._construct:
976 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200977 elif self._tlv:
978 self._tlv.from_dict(abstract_data)
979 return self._tlv.to_tlv()
Harald Welte1aae4a02021-10-14 16:12:42 +0200980 raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100981
Harald Welteee3501f2021-04-02 13:00:18 +0200982 def _decode_bin(self, raw_bin_data:bytearray):
Harald Welteb2edd142021-01-08 23:29:35 +0100983 chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)]
984 return [self.decode_record_bin(x) for x in chunks]
985
Harald Welteee3501f2021-04-02 13:00:18 +0200986 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +0100987 chunks = [self.encode_record_bin(x) for x in abstract_data]
988 # FIXME: pad to file size
989 return b''.join(chunks)
990
991
Harald Welte917d98c2021-04-21 11:51:25 +0200992class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +0200993 """BER-TLV EF (Entry File) in the smart card filesystem.
994 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +0100995
Harald Welte27881622021-04-21 11:16:31 +0200996 NOTE: We currently don't really support those, this class is simply a wrapper
997 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
998 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +0100999
Harald Welte917d98c2021-04-21 11:51:25 +02001000 @with_default_category('BER-TLV EF Commands')
1001 class ShellCommands(CommandSet):
1002 """Shell commands specific for BER-TLV EFs."""
1003 def __init__(self):
1004 super().__init__()
1005
1006 retrieve_data_parser = argparse.ArgumentParser()
1007 retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1008 @cmd2.with_argparser(retrieve_data_parser)
1009 def do_retrieve_data(self, opts):
1010 """Retrieve (Read) data from a BER-TLV EF"""
1011 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1012 self._cmd.poutput(data)
1013
1014 def do_retrieve_tags(self, opts):
1015 """List tags available in a given BER-TLV EF"""
1016 tags = self._cmd.rs.retrieve_tags()
1017 self._cmd.poutput(tags)
1018
1019 set_data_parser = argparse.ArgumentParser()
1020 set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1021 set_data_parser.add_argument('data', help='Data bytes (hex format) to write')
1022 @cmd2.with_argparser(set_data_parser)
1023 def do_set_data(self, opts):
1024 """Set (Write) data for a given tag in a BER-TLV EF"""
1025 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1026 if data:
1027 self._cmd.poutput(data)
1028
1029 del_data_parser = argparse.ArgumentParser()
1030 del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set')
1031 @cmd2.with_argparser(del_data_parser)
1032 def do_delete_data(self, opts):
1033 """Delete data for a given tag in a BER-TLV EF"""
1034 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1035 if data:
1036 self._cmd.poutput(data)
1037
1038
1039 def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
1040 size={1,None}):
1041 """
1042 Args:
1043 fid : File Identifier (4 hex digits)
1044 sfid : Short File Identifier (2 hex digits, optional)
1045 name : Brief name of the file, lik EF_ICCID
1046 desc : Description of the file
1047 parent : Parent CardFile object within filesystem hierarchy
1048 size : tuple of (minimum_size, recommended_size)
1049 """
1050 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
1051 self._construct = None
1052 self.size = size
1053 self.shell_commands = [self.ShellCommands()]
1054
Harald Welteb2edd142021-01-08 23:29:35 +01001055
1056class RuntimeState(object):
1057 """Represent the runtime state of a session with a card."""
Harald Welteee3501f2021-04-02 13:00:18 +02001058 def __init__(self, card, profile:'CardProfile'):
1059 """
1060 Args:
1061 card : pysim.cards.Card instance
1062 profile : CardProfile instance
1063 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001064 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001065 self.card = card
Harald Weltefe8a7442021-04-10 11:51:54 +02001066 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001067 self.profile = profile
Philipp Maier51cad0d2021-11-08 15:45:10 +01001068
1069 # make sure the class and selection control bytes, which are specified
1070 # by the card profile are used
1071 self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
1072
Harald Welte5ce35242021-04-02 20:27:05 +02001073 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001074 apps = self._match_applications()
1075 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001076 if a.adf:
1077 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001078 for f in self.profile.files_in_mf:
1079 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001080 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001081
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001082 # make sure that when the runtime state is created, the card is also
1083 # in a defined state.
1084 self.reset()
1085
Philipp Maier1e896f32021-03-10 17:02:53 +01001086 def _match_applications(self):
1087 """match the applications from the profile with applications on the card"""
1088 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001089
1090 # When the profile does not feature any applications, then we are done already
1091 if not apps_profile:
1092 return []
1093
1094 # Read AIDs from card and match them against the applications defined by the
1095 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001096 aids_card = self.card.read_aids()
1097 apps_taken = []
1098 if aids_card:
1099 aids_taken = []
1100 print("AIDs on card:")
1101 for a in aids_card:
1102 for f in apps_profile:
1103 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001104 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001105 aids_taken.append(a)
1106 apps_taken.append(f)
1107 aids_unknown = set(aids_card) - set(aids_taken)
1108 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001109 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001110 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001111 print("warning: EF.DIR seems to be empty!")
1112
1113 # Some card applications may not be registered in EF.DIR, we will actively
1114 # probe for those applications
1115 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001116 try:
1117 data, sw = self.card.select_adf_by_aid(f.aid)
1118 if sw == "9000":
1119 print(" %s: %s" % (f.name, f.aid))
1120 apps_taken.append(f)
1121 except SwMatchError:
1122 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001123 return apps_taken
1124
Harald Weltedaf2b392021-05-03 23:17:29 +02001125 def reset(self, cmd_app=None) -> Hexstr:
1126 """Perform physical card reset and obtain ATR.
1127 Args:
1128 cmd_app : Command Application State (for unregistering old file commands)
1129 """
Philipp Maier946226a2021-10-29 18:31:03 +02001130 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001131 # select MF to reset internal state and to verify card really works
1132 self.select('MF', cmd_app)
1133 return atr
1134
Harald Welteee3501f2021-04-02 13:00:18 +02001135 def get_cwd(self) -> CardDF:
1136 """Obtain the current working directory.
1137
1138 Returns:
1139 CardDF instance
1140 """
Harald Welteb2edd142021-01-08 23:29:35 +01001141 if isinstance(self.selected_file, CardDF):
1142 return self.selected_file
1143 else:
1144 return self.selected_file.parent
1145
Harald Welte5ce35242021-04-02 20:27:05 +02001146 def get_application_df(self) -> Optional[CardADF]:
1147 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001148
1149 Returns:
1150 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001151 # iterate upwards from selected file; check if any is an ADF
1152 node = self.selected_file
1153 while node.parent != node:
1154 if isinstance(node, CardADF):
1155 return node
1156 node = node.parent
1157 return None
1158
Harald Welteee3501f2021-04-02 13:00:18 +02001159 def interpret_sw(self, sw:str):
1160 """Interpret a given status word relative to the currently selected application
1161 or the underlying card profile.
1162
1163 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001164 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001165
1166 Returns:
1167 Tuple of two strings
1168 """
Harald Welte86fbd392021-04-02 22:13:09 +02001169 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001170 adf = self.get_application_df()
1171 if adf:
1172 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001173 # The application either comes with its own interpret_sw
1174 # method or we will use the interpret_sw method from the
1175 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001176 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001177 res = app.interpret_sw(sw)
1178 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001179
Harald Welteee3501f2021-04-02 13:00:18 +02001180 def probe_file(self, fid:str, cmd_app=None):
1181 """Blindly try to select a file and automatically add a matching file
1182 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001183 if not is_hex(fid, 4, 4):
1184 raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
1185
1186 try:
1187 (data, sw) = self.card._scc.select_file(fid)
1188 except SwMatchError as swm:
1189 k = self.interpret_sw(swm.sw_actual)
1190 if not k:
1191 raise(swm)
1192 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1193
1194 select_resp = self.selected_file.decode_select_response(data)
1195 if (select_resp['file_descriptor']['file_type'] == 'df'):
1196 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime")
1197 else:
1198 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec9cdce32021-04-11 10:28:28 +02001199 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 +01001200 else:
Harald Weltec9cdce32021-04-11 10:28:28 +02001201 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 +01001202
1203 self.selected_file.add_files([f])
1204 self.selected_file = f
1205 return select_resp
1206
Harald Welteee3501f2021-04-02 13:00:18 +02001207 def select(self, name:str, cmd_app=None):
1208 """Select a file (EF, DF, ADF, MF, ...).
1209
1210 Args:
1211 name : Name of file to select
1212 cmd_app : Command Application State (for unregistering old file commands)
1213 """
Harald Welteb2edd142021-01-08 23:29:35 +01001214 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001215 if is_hex(name):
1216 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001217
1218 # unregister commands of old file
1219 if cmd_app and self.selected_file.shell_commands:
1220 for c in self.selected_file.shell_commands:
1221 cmd_app.unregister_command_set(c)
1222
Harald Welteb2edd142021-01-08 23:29:35 +01001223 if name in sels:
1224 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001225 try:
1226 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001227 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001228 else:
1229 (data, sw) = self.card._scc.select_file(f.fid)
1230 self.selected_file = f
1231 except SwMatchError as swm:
1232 k = self.interpret_sw(swm.sw_actual)
1233 if not k:
1234 raise(swm)
1235 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001236 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001237 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001238 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001239 # store the decoded FCP for later reference
1240 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001241
1242 # register commands of new file
1243 if cmd_app and self.selected_file.shell_commands:
1244 for c in self.selected_file.shell_commands:
1245 cmd_app.register_command_set(c)
1246
1247 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001248
Harald Welte34b05d32021-05-25 22:03:13 +02001249 def status(self):
1250 """Request STATUS (current selected file FCP) from card."""
1251 (data, sw) = self.card._scc.status()
1252 return self.selected_file.decode_select_response(data)
1253
Harald Welte485692b2021-05-25 22:21:44 +02001254 def activate_file(self, name:str):
1255 """Request ACTIVATE FILE of specified file."""
1256 sels = self.selected_file.get_selectables()
1257 f = sels[name]
1258 data, sw = self.card._scc.activate_file(f.fid)
1259 return data, sw
1260
Harald Welteee3501f2021-04-02 13:00:18 +02001261 def read_binary(self, length:int=None, offset:int=0):
1262 """Read [part of] a transparent EF binary data.
1263
1264 Args:
1265 length : Amount of data to read (None: as much as possible)
1266 offset : Offset into the file from which to read 'length' bytes
1267 Returns:
1268 binary data read from the file
1269 """
Harald Welteb2edd142021-01-08 23:29:35 +01001270 if not isinstance(self.selected_file, TransparentEF):
1271 raise TypeError("Only works with TransparentEF")
1272 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1273
Harald Welte2d4a64b2021-04-03 09:01:24 +02001274 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001275 """Read [part of] a transparent EF binary data and decode it.
1276
1277 Args:
1278 length : Amount of data to read (None: as much as possible)
1279 offset : Offset into the file from which to read 'length' bytes
1280 Returns:
1281 abstract decode data read from the file
1282 """
Harald Welteb2edd142021-01-08 23:29:35 +01001283 (data, sw) = self.read_binary()
1284 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001285 return (dec_data, sw)
1286
Harald Welteee3501f2021-04-02 13:00:18 +02001287 def update_binary(self, data_hex:str, offset:int=0):
1288 """Update transparent EF binary data.
1289
1290 Args:
1291 data_hex : hex string of data to be written
1292 offset : Offset into the file from which to write 'data_hex'
1293 """
Harald Welteb2edd142021-01-08 23:29:35 +01001294 if not isinstance(self.selected_file, TransparentEF):
1295 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001296 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001297
Harald Welteee3501f2021-04-02 13:00:18 +02001298 def update_binary_dec(self, data:dict):
1299 """Update transparent EF from abstract data. Encodes the data to binary and
1300 then updates the EF with it.
1301
1302 Args:
1303 data : abstract data which is to be encoded and written
1304 """
Harald Welteb2edd142021-01-08 23:29:35 +01001305 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001306 return self.update_binary(data_hex)
1307
Harald Welteee3501f2021-04-02 13:00:18 +02001308 def read_record(self, rec_nr:int=0):
1309 """Read a record as binary data.
1310
1311 Args:
1312 rec_nr : Record number to read
1313 Returns:
1314 hex string of binary data contained in record
1315 """
Harald Welteb2edd142021-01-08 23:29:35 +01001316 if not isinstance(self.selected_file, LinFixedEF):
1317 raise TypeError("Only works with Linear Fixed EF")
1318 # returns a string of hex nibbles
1319 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1320
Harald Welteee3501f2021-04-02 13:00:18 +02001321 def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]:
1322 """Read a record and decode it to abstract data.
1323
1324 Args:
1325 rec_nr : Record number to read
1326 Returns:
1327 abstract data contained in record
1328 """
Harald Welteb2edd142021-01-08 23:29:35 +01001329 (data, sw) = self.read_record(rec_nr)
1330 return (self.selected_file.decode_record_hex(data), sw)
1331
Harald Welteee3501f2021-04-02 13:00:18 +02001332 def update_record(self, rec_nr:int, data_hex:str):
1333 """Update a record with given binary data
1334
1335 Args:
1336 rec_nr : Record number to read
1337 data_hex : Hex string binary data to be written
1338 """
Harald Welteb2edd142021-01-08 23:29:35 +01001339 if not isinstance(self.selected_file, LinFixedEF):
1340 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001341 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 +01001342
Harald Welteee3501f2021-04-02 13:00:18 +02001343 def update_record_dec(self, rec_nr:int, data:dict):
1344 """Update a record with given abstract data. Will encode abstract to binary data
1345 and then write it to the given record on the card.
1346
1347 Args:
1348 rec_nr : Record number to read
1349 data_hex : Abstract data to be written
1350 """
Harald Welte1e456572021-04-02 17:16:30 +02001351 data_hex = self.selected_file.encode_record_hex(data)
1352 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001353
Harald Welte917d98c2021-04-21 11:51:25 +02001354 def retrieve_data(self, tag:int=0):
1355 """Read a DO/TLV as binary data.
1356
1357 Args:
1358 tag : Tag of TLV/DO to read
1359 Returns:
1360 hex string of full BER-TLV DO including Tag and Length
1361 """
1362 if not isinstance(self.selected_file, BerTlvEF):
1363 raise TypeError("Only works with BER-TLV EF")
1364 # returns a string of hex nibbles
1365 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1366
1367 def retrieve_tags(self):
1368 """Retrieve tags available on BER-TLV EF.
1369
1370 Returns:
1371 list of integer tags contained in EF
1372 """
1373 if not isinstance(self.selected_file, BerTlvEF):
1374 raise TypeError("Only works with BER-TLV EF")
1375 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001376 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001377 return list(value)
1378
1379 def set_data(self, tag:int, data_hex:str):
1380 """Update a TLV/DO with given binary data
1381
1382 Args:
1383 tag : Tag of TLV/DO to be written
1384 data_hex : Hex string binary data to be written (value portion)
1385 """
1386 if not isinstance(self.selected_file, BerTlvEF):
1387 raise TypeError("Only works with BER-TLV EF")
1388 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1389
Philipp Maier5d698e52021-09-16 13:18:01 +02001390 def unregister_cmds(self, cmd_app=None):
1391 """Unregister all file specific commands."""
1392 if cmd_app and self.selected_file.shell_commands:
1393 for c in self.selected_file.shell_commands:
1394 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001395
Harald Welteb2edd142021-01-08 23:29:35 +01001396
1397
1398class FileData(object):
1399 """Represent the runtime, on-card data."""
1400 def __init__(self, fdesc):
1401 self.desc = fdesc
1402 self.fcp = None
1403
1404
Harald Welteee3501f2021-04-02 13:00:18 +02001405def interpret_sw(sw_data:dict, sw:str):
1406 """Interpret a given status word.
1407
1408 Args:
1409 sw_data : Hierarchical dict of status word matches
1410 sw : status word to match (string of 4 hex digits)
1411 Returns:
1412 tuple of two strings (class_string, description)
1413 """
Harald Welteb2edd142021-01-08 23:29:35 +01001414 for class_str, swdict in sw_data.items():
1415 # first try direct match
1416 if sw in swdict:
1417 return (class_str, swdict[sw])
1418 # next try wildcard matches
1419 for pattern, descr in swdict.items():
1420 if sw_match(sw, pattern):
1421 return (class_str, descr)
1422 return None
1423
1424class CardApplication(object):
1425 """A card application is represented by an ADF (with contained hierarchy) and optionally
1426 some SW definitions."""
Harald Welte5ce35242021-04-02 20:27:05 +02001427 def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001428 """
1429 Args:
1430 adf : ADF name
1431 sw : Dict of status word conversions
1432 """
Harald Welteb2edd142021-01-08 23:29:35 +01001433 self.name = name
1434 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001435 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001436 # back-reference from ADF to Applicaiton
1437 if self.adf:
1438 self.aid = aid or self.adf.aid
1439 self.adf.application = self
1440 else:
1441 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001442
1443 def __str__(self):
1444 return "APP(%s)" % (self.name)
1445
1446 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001447 """Interpret a given status word within the application.
1448
1449 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001450 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001451
1452 Returns:
1453 Tuple of two strings
1454 """
Harald Welteb2edd142021-01-08 23:29:35 +01001455 return interpret_sw(self.sw, sw)
1456
Harald Weltef44256c2021-10-14 15:53:39 +02001457
1458class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001459 """A specific card model, typically having some additional vendor-specific files. All
1460 you need to do is to define a sub-class with a list of ATRs or an overridden match
1461 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001462 _atrs = []
1463
1464 @classmethod
1465 @abc.abstractmethod
1466 def add_files(cls, rs:RuntimeState):
1467 """Add model specific files to given RuntimeState."""
1468
1469 @classmethod
1470 def match(cls, scc:SimCardCommands) -> bool:
1471 """Test if given card matches this model."""
1472 card_atr = scc.get_atr()
1473 for atr in cls._atrs:
1474 atr_bin = toBytes(atr)
1475 if atr_bin == card_atr:
1476 print("Detected CardModel:", cls.__name__)
1477 return True
1478 return False
1479
1480 @staticmethod
1481 def apply_matching_models(scc:SimCardCommands, rs:RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001482 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1483 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1484 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001485 for m in CardModel.__subclasses__():
1486 if m.match(scc):
1487 m.add_files(rs)