blob: b8b6152e7cceef12cc610515471cd54d0677cf3b [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
Harald Weltec91085e2022-02-10 18:05:45 +010047
Harald Welteb2edd142021-01-08 23:29:35 +010048class CardFile(object):
49 """Base class for all objects in the smart card filesystem.
50 Serve as a common ancestor to all other file types; rarely used directly.
51 """
52 RESERVED_NAMES = ['..', '.', '/', 'MF']
53 RESERVED_FIDS = ['3f00']
54
Harald Weltec91085e2022-02-10 18:05:45 +010055 def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
56 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
Harald Welteee3501f2021-04-02 13:00:18 +020057 """
58 Args:
59 fid : File Identifier (4 hex digits)
60 sfid : Short File Identifier (2 hex digits, optional)
61 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020062 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020063 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier5af7bdf2021-11-04 12:48:41 +010064 profile : Card profile that this file should be part of
Harald Welteee3501f2021-04-02 13:00:18 +020065 """
Harald Welteb2edd142021-01-08 23:29:35 +010066 if not isinstance(self, CardADF) and fid == None:
67 raise ValueError("fid is mandatory")
68 if fid:
69 fid = fid.lower()
70 self.fid = fid # file identifier
71 self.sfid = sfid # short file identifier
72 self.name = name # human readable name
73 self.desc = desc # human readable description
74 self.parent = parent
75 if self.parent and self.parent != self and self.fid:
76 self.parent.add_file(self)
Philipp Maier5af7bdf2021-11-04 12:48:41 +010077 self.profile = profile
Harald Weltec91085e2022-02-10 18:05:45 +010078 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +010079
Harald Weltec91085e2022-02-10 18:05:45 +010080 # Note: the basic properties (fid, name, ect.) are verified when
81 # the file is attached to a parent file. See method add_file() in
82 # class Card DF
Philipp Maier66061582021-03-09 21:57:57 +010083
Harald Welteb2edd142021-01-08 23:29:35 +010084 def __str__(self):
85 if self.name:
86 return self.name
87 else:
88 return self.fid
89
Harald Weltec91085e2022-02-10 18:05:45 +010090 def _path_element(self, prefer_name: bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +010091 if prefer_name and self.name:
92 return self.name
93 else:
94 return self.fid
95
Harald Weltec91085e2022-02-10 18:05:45 +010096 def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +020097 """Return fully qualified path to file as list of FID or name strings.
98
99 Args:
100 prefer_name : Preferably build path of names; fall-back to FIDs as required
101 """
Harald Welte1e456572021-04-02 17:16:30 +0200102 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +0100103 ret = self.parent.fully_qualified_path(prefer_name)
104 else:
105 ret = []
Harald Welte1e456572021-04-02 17:16:30 +0200106 elem = self._path_element(prefer_name)
107 if elem:
108 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100109 return ret
110
Harald Welteee3501f2021-04-02 13:00:18 +0200111 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100112 """Return the MF (root) of the file system."""
113 if self.parent == None:
114 return None
115 # iterate towards the top. MF has parent == self
116 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200117 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100118 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200119 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100120
Harald Weltec91085e2022-02-10 18:05:45 +0100121 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200122 """Return a dict of {'identifier': self} tuples.
123
124 Args:
125 alias : Add an alias with given name to 'self'
126 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
127 If not specified, all selectables will be returned.
128 Returns:
129 dict containing reference to 'self' for all identifiers.
130 """
Harald Welteb2edd142021-01-08 23:29:35 +0100131 sels = {}
132 if alias:
133 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100134 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100135 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100136 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100137 sels.update({self.name: self})
138 return sels
139
Harald Weltec91085e2022-02-10 18:05:45 +0100140 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200141 """Return a dict of {'identifier': File} that is selectable from the current file.
142
143 Args:
144 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
145 If not specified, all selectables will be returned.
146 Returns:
147 dict containing all selectable items. Key is identifier (string), value
148 a reference to a CardFile (or derived class) instance.
149 """
Philipp Maier786f7812021-02-25 16:48:10 +0100150 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100151 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100152 if flags == [] or 'SELF' in flags:
153 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100154 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100155 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200156 if self.parent:
157 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100158 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100159 if flags == [] or 'MF' in flags:
160 mf = self.get_mf()
161 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100162 sels.update(mf._get_self_selectables(flags=flags))
163 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100164 return sels
165
Harald Weltec91085e2022-02-10 18:05:45 +0100166 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200167 """Return a dict of {'identifier': File} that is selectable from the current file.
168
169 Args:
170 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
171 If not specified, all selectables will be returned.
172 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200173 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200174 """
Philipp Maier786f7812021-02-25 16:48:10 +0100175 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100176 sel_keys = list(sels.keys())
177 sel_keys.sort()
178 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100179
Harald Weltec91085e2022-02-10 18:05:45 +0100180 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100181 """Decode the response to a SELECT command.
182
183 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100184 data_hex: Hex string of the select response
185 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100186
Harald Weltec91085e2022-02-10 18:05:45 +0100187 # When the current file does not implement a custom select response decoder,
188 # we just ask the parent file to decode the select response. If this method
189 # is not overloaded by the current file we will again ask the parent file.
190 # This way we recursively travel up the file system tree until we hit a file
191 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200192 if self.parent:
193 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100194
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100195 def get_profile(self):
196 """Get the profile associated with this file. If this file does not have any
197 profile assigned, try to find a file above (usually the MF) in the filesystem
198 hirarchy that has a profile assigned
199 """
200
201 # If we have a profile set, return it
202 if self.profile:
203 return self.profile
204
205 # Walk up recursively until we hit a parent that has a profile set
206 if self.parent:
207 return self.parent.get_profile()
208 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100209
Harald Weltec91085e2022-02-10 18:05:45 +0100210
Harald Welteb2edd142021-01-08 23:29:35 +0100211class CardDF(CardFile):
212 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100213
214 @with_default_category('DF/ADF Commands')
215 class ShellCommands(CommandSet):
216 def __init__(self):
217 super().__init__()
218
Harald Welteb2edd142021-01-08 23:29:35 +0100219 def __init__(self, **kwargs):
220 if not isinstance(self, CardADF):
221 if not 'fid' in kwargs:
222 raise TypeError('fid is mandatory for all DF')
223 super().__init__(**kwargs)
224 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100225 self.shell_commands = [self.ShellCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +0100226
227 def __str__(self):
228 return "DF(%s)" % (super().__str__())
229
Harald Weltec91085e2022-02-10 18:05:45 +0100230 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200231 """Add a child (DF/EF) to this DF.
232 Args:
233 child: The new DF/EF to be added
234 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
235 """
Harald Welteb2edd142021-01-08 23:29:35 +0100236 if not isinstance(child, CardFile):
237 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100238 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100239 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100240 if child.name in CardFile.RESERVED_NAMES:
241 raise ValueError("File name %s is a reserved name" % (child.name))
242 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100243 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100244 if child.fid in self.children:
245 if ignore_existing:
246 return
Harald Weltec91085e2022-02-10 18:05:45 +0100247 raise ValueError(
248 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100249 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100250 raise ValueError(
251 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100252 if self.lookup_file_by_name(child.name):
253 if ignore_existing:
254 return
Harald Weltec91085e2022-02-10 18:05:45 +0100255 raise ValueError(
256 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100257 self.children[child.fid] = child
258 child.parent = self
259
Harald Weltec91085e2022-02-10 18:05:45 +0100260 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200261 """Add a list of child (DF/EF) to this DF
262
263 Args:
264 children: List of new DF/EFs to be added
265 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
266 """
Harald Welteb2edd142021-01-08 23:29:35 +0100267 for child in children:
268 self.add_file(child, ignore_existing)
269
Harald Weltec91085e2022-02-10 18:05:45 +0100270 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200271 """Return a dict of {'identifier': File} that is selectable from the current DF.
272
273 Args:
274 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
275 If not specified, all selectables will be returned.
276 Returns:
277 dict containing all selectable items. Key is identifier (string), value
278 a reference to a CardFile (or derived class) instance.
279 """
Harald Welteb2edd142021-01-08 23:29:35 +0100280 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100281 sels = super().get_selectables(flags)
282 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100283 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100284 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100285 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100286 return sels
287
Harald Weltec91085e2022-02-10 18:05:45 +0100288 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200289 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100290 if name == None:
291 return None
292 for i in self.children.values():
293 if i.name and i.name == name:
294 return i
295 return None
296
Harald Weltec91085e2022-02-10 18:05:45 +0100297 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200298 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100299 if sfid == None:
300 return None
301 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200302 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100303 return i
304 return None
305
Harald Weltec91085e2022-02-10 18:05:45 +0100306 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200307 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100308 if fid in self.children:
309 return self.children[fid]
310 return None
311
312
313class CardMF(CardDF):
314 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100315
Harald Welteb2edd142021-01-08 23:29:35 +0100316 def __init__(self, **kwargs):
317 # can be overridden; use setdefault
318 kwargs.setdefault('fid', '3f00')
319 kwargs.setdefault('name', 'MF')
320 kwargs.setdefault('desc', 'Master File (directory root)')
321 # cannot be overridden; use assignment
322 kwargs['parent'] = self
323 super().__init__(**kwargs)
324 self.applications = dict()
325
326 def __str__(self):
327 return "MF(%s)" % (self.fid)
328
Harald Weltec91085e2022-02-10 18:05:45 +0100329 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200330 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100331 if not isinstance(app, CardADF):
332 raise TypeError("Expected an ADF instance")
333 if app.aid in self.applications:
334 raise ValueError("AID %s already exists" % (app.aid))
335 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100336 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100337
338 def get_app_names(self):
339 """Get list of completions (AID names)"""
340 return [x.name for x in self.applications]
341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200343 """Return a dict of {'identifier': File} that is selectable from the current DF.
344
345 Args:
346 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
347 If not specified, all selectables will be returned.
348 Returns:
349 dict containing all selectable items. Key is identifier (string), value
350 a reference to a CardFile (or derived class) instance.
351 """
Philipp Maier786f7812021-02-25 16:48:10 +0100352 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100353 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100354 return sels
355
Harald Weltec91085e2022-02-10 18:05:45 +0100356 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100357 """Get applications by AID + name"""
358 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100359 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100360 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100361 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100362 sels.update(
363 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100364 return sels
365
Harald Weltec9752512022-02-11 16:31:15 +0100366 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200367 """Decode the response to a SELECT command.
368
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100369 This is the fall-back method which automatically defers to the standard decoding
370 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100371 performed. Specific derived classes (usually ADF) can overload this method to
372 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200373 """
Harald Welteb2edd142021-01-08 23:29:35 +0100374
Harald Weltec9752512022-02-11 16:31:15 +0100375 if not data_hex:
376 return data_hex
377
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100378 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100379
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100380 if profile:
381 return profile.decode_select_response(data_hex)
382 else:
383 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100384
Harald Weltec91085e2022-02-10 18:05:45 +0100385
Harald Welteb2edd142021-01-08 23:29:35 +0100386class CardADF(CardDF):
387 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100388
389 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100390 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200391 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200392 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100393 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200394 mf = self.get_mf()
395 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200396 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100397
398 def __str__(self):
399 return "ADF(%s)" % (self.aid)
400
Harald Weltec91085e2022-02-10 18:05:45 +0100401 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100402 if self.name and prefer_name:
403 return self.name
404 else:
405 return self.aid
406
407
408class CardEF(CardFile):
409 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100410
Harald Welteb2edd142021-01-08 23:29:35 +0100411 def __init__(self, *, fid, **kwargs):
412 kwargs['fid'] = fid
413 super().__init__(**kwargs)
414
415 def __str__(self):
416 return "EF(%s)" % (super().__str__())
417
Harald Weltec91085e2022-02-10 18:05:45 +0100418 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200419 """Return a dict of {'identifier': File} that is selectable from the current DF.
420
421 Args:
422 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
423 If not specified, all selectables will be returned.
424 Returns:
425 dict containing all selectable items. Key is identifier (string), value
426 a reference to a CardFile (or derived class) instance.
427 """
Harald Weltec91085e2022-02-10 18:05:45 +0100428 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100429 sels = super().get_selectables(flags)
Harald Weltec91085e2022-02-10 18:05:45 +0100430 sels.update(
431 {x.name: x for x in self.parent.children.values() if x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100432 return sels
433
434
435class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200436 """Transparent EF (Entry File) in the smart card filesystem.
437
438 A Transparent EF is a binary file with no formal structure. This is contrary to
439 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100440
441 @with_default_category('Transparent EF Commands')
442 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200443 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100444
Harald Welteb2edd142021-01-08 23:29:35 +0100445 def __init__(self):
446 super().__init__()
447
448 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100449 read_bin_parser.add_argument(
450 '--offset', type=int, default=0, help='Byte offset for start of read')
451 read_bin_parser.add_argument(
452 '--length', type=int, help='Number of bytes to read')
453
Harald Welteb2edd142021-01-08 23:29:35 +0100454 @cmd2.with_argparser(read_bin_parser)
455 def do_read_binary(self, opts):
456 """Read binary data from a transparent EF"""
457 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
458 self._cmd.poutput(data)
459
Harald Weltebcad86c2021-04-06 20:08:39 +0200460 read_bin_dec_parser = argparse.ArgumentParser()
461 read_bin_dec_parser.add_argument('--oneline', action='store_true',
462 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100463
Harald Weltebcad86c2021-04-06 20:08:39 +0200464 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100465 def do_read_binary_decoded(self, opts):
466 """Read + decode data from a transparent EF"""
467 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200468 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100469
470 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100471 upd_bin_parser.add_argument(
472 '--offset', type=int, default=0, help='Byte offset for start of read')
473 upd_bin_parser.add_argument(
474 'data', help='Data bytes (hex format) to write')
475
Harald Welteb2edd142021-01-08 23:29:35 +0100476 @cmd2.with_argparser(upd_bin_parser)
477 def do_update_binary(self, opts):
478 """Update (Write) data of a transparent EF"""
479 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100480 if data:
481 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100482
483 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100484 upd_bin_dec_parser.add_argument(
485 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200486 upd_bin_dec_parser.add_argument('--json-path', type=str,
487 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100488
Harald Welteb2edd142021-01-08 23:29:35 +0100489 @cmd2.with_argparser(upd_bin_dec_parser)
490 def do_update_binary_decoded(self, opts):
491 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200492 if opts.json_path:
493 (data_json, sw) = self._cmd.rs.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100494 js_path_modify(data_json, opts.json_path,
495 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200496 else:
497 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100498 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100499 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200500 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100501
Harald Welte4145d3c2021-04-08 20:34:13 +0200502 def do_edit_binary_decoded(self, opts):
503 """Edit the JSON representation of the EF contents in an editor."""
504 (orig_json, sw) = self._cmd.rs.read_binary_dec()
505 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
506 filename = '%s/file' % dirname
507 # write existing data as JSON to file
508 with open(filename, 'w') as text_file:
509 json.dump(orig_json, text_file, indent=4)
510 # run a text editor
511 self._cmd._run_editor(filename)
512 with open(filename, 'r') as text_file:
513 edited_json = json.load(text_file)
514 if edited_json == orig_json:
515 self._cmd.poutput("Data not modified, skipping write")
516 else:
517 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
518 if data:
519 self._cmd.poutput_json(data)
520
Harald Weltec91085e2022-02-10 18:05:45 +0100521 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
522 size={1, None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200523 """
524 Args:
525 fid : File Identifier (4 hex digits)
526 sfid : Short File Identifier (2 hex digits, optional)
527 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200528 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200529 parent : Parent CardFile object within filesystem hierarchy
530 size : tuple of (minimum_size, recommended_size)
531 """
Harald Welteb2edd142021-01-08 23:29:35 +0100532 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200533 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200534 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100535 self.size = size
536 self.shell_commands = [self.ShellCommands()]
537
Harald Weltec91085e2022-02-10 18:05:45 +0100538 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200539 """Decode raw (binary) data into abstract representation.
540
541 A derived class would typically provide a _decode_bin() or _decode_hex() method
542 for implementing this specifically for the given file. This function checks which
543 of the method exists, add calls them (with conversion, as needed).
544
545 Args:
546 raw_bin_data : binary encoded data
547 Returns:
548 abstract_data; dict representing the decoded data
549 """
Harald Welteb2edd142021-01-08 23:29:35 +0100550 method = getattr(self, '_decode_bin', None)
551 if callable(method):
552 return method(raw_bin_data)
553 method = getattr(self, '_decode_hex', None)
554 if callable(method):
555 return method(b2h(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:
Harald Welteca60ac22022-02-10 18:01:02 +0100559 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100560 t.from_tlv(raw_bin_data)
561 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100562 return {'raw': raw_bin_data.hex()}
563
Harald Weltec91085e2022-02-10 18:05:45 +0100564 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200565 """Decode raw (hex string) data into abstract representation.
566
567 A derived class would typically provide a _decode_bin() or _decode_hex() method
568 for implementing this specifically for the given file. This function checks which
569 of the method exists, add calls them (with conversion, as needed).
570
571 Args:
572 raw_hex_data : hex-encoded data
573 Returns:
574 abstract_data; dict representing the decoded data
575 """
Harald Welteb2edd142021-01-08 23:29:35 +0100576 method = getattr(self, '_decode_hex', None)
577 if callable(method):
578 return method(raw_hex_data)
579 raw_bin_data = h2b(raw_hex_data)
580 method = getattr(self, '_decode_bin', None)
581 if callable(method):
582 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200583 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200584 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200585 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100586 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100587 t.from_tlv(raw_bin_data)
588 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100589 return {'raw': raw_bin_data.hex()}
590
Harald Weltec91085e2022-02-10 18:05:45 +0100591 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200592 """Encode abstract representation into raw (binary) data.
593
594 A derived class would typically provide an _encode_bin() or _encode_hex() method
595 for implementing this specifically for the given file. This function checks which
596 of the method exists, add calls them (with conversion, as needed).
597
598 Args:
599 abstract_data : dict representing the decoded data
600 Returns:
601 binary encoded data
602 """
Harald Welteb2edd142021-01-08 23:29:35 +0100603 method = getattr(self, '_encode_bin', None)
604 if callable(method):
605 return method(abstract_data)
606 method = getattr(self, '_encode_hex', None)
607 if callable(method):
608 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200609 if self._construct:
610 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200611 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100612 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100613 t.from_dict(abstract_data)
614 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100615 raise NotImplementedError(
616 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100617
Harald Weltec91085e2022-02-10 18:05:45 +0100618 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200619 """Encode abstract representation into raw (hex string) data.
620
621 A derived class would typically provide an _encode_bin() or _encode_hex() method
622 for implementing this specifically for the given file. This function checks which
623 of the method exists, add calls them (with conversion, as needed).
624
625 Args:
626 abstract_data : dict representing the decoded data
627 Returns:
628 hex string encoded data
629 """
Harald Welteb2edd142021-01-08 23:29:35 +0100630 method = getattr(self, '_encode_hex', None)
631 if callable(method):
632 return method(abstract_data)
633 method = getattr(self, '_encode_bin', None)
634 if callable(method):
635 raw_bin_data = method(abstract_data)
636 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200637 if self._construct:
638 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200639 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100640 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100641 t.from_dict(abstract_data)
642 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100643 raise NotImplementedError(
644 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100645
646
647class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200648 """Linear Fixed EF (Entry File) in the smart card filesystem.
649
650 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
651 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100652
653 @with_default_category('Linear Fixed EF Commands')
654 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200655 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100656
Harald Welteb2edd142021-01-08 23:29:35 +0100657 def __init__(self):
658 super().__init__()
659
660 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100661 read_rec_parser.add_argument(
662 'record_nr', type=int, help='Number of record to be read')
663 read_rec_parser.add_argument(
664 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
665
Harald Welteb2edd142021-01-08 23:29:35 +0100666 @cmd2.with_argparser(read_rec_parser)
667 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100668 """Read one or multiple records from a record-oriented EF"""
669 for r in range(opts.count):
670 recnr = opts.record_nr + r
671 (data, sw) = self._cmd.rs.read_record(recnr)
672 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100673 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100674 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100675 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100676 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100677
678 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100679 read_rec_dec_parser.add_argument(
680 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200681 read_rec_dec_parser.add_argument('--oneline', action='store_true',
682 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100683
Harald Welteb2edd142021-01-08 23:29:35 +0100684 @cmd2.with_argparser(read_rec_dec_parser)
685 def do_read_record_decoded(self, opts):
686 """Read + decode a record from a record-oriented EF"""
687 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200688 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100689
Harald Welte850b72a2021-04-07 09:33:03 +0200690 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100691
Harald Welte850b72a2021-04-07 09:33:03 +0200692 @cmd2.with_argparser(read_recs_parser)
693 def do_read_records(self, opts):
694 """Read all records from a record-oriented EF"""
695 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
696 for recnr in range(1, 1 + num_of_rec):
697 (data, sw) = self._cmd.rs.read_record(recnr)
698 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100699 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200700 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100701 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200702 self._cmd.poutput("%03d %s" % (recnr, recstr))
703
704 read_recs_dec_parser = argparse.ArgumentParser()
705 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100706 help='No JSON pretty-printing, dump as a single line')
707
Harald Welte850b72a2021-04-07 09:33:03 +0200708 @cmd2.with_argparser(read_recs_dec_parser)
709 def do_read_records_decoded(self, opts):
710 """Read + decode all records from a record-oriented EF"""
711 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
712 # collect all results in list so they are rendered as JSON list when printing
713 data_list = []
714 for recnr in range(1, 1 + num_of_rec):
715 (data, sw) = self._cmd.rs.read_record_dec(recnr)
716 data_list.append(data)
717 self._cmd.poutput_json(data_list, opts.oneline)
718
Harald Welteb2edd142021-01-08 23:29:35 +0100719 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100720 upd_rec_parser.add_argument(
721 'record_nr', type=int, help='Number of record to be read')
722 upd_rec_parser.add_argument(
723 'data', help='Data bytes (hex format) to write')
724
Harald Welteb2edd142021-01-08 23:29:35 +0100725 @cmd2.with_argparser(upd_rec_parser)
726 def do_update_record(self, opts):
727 """Update (write) data to a record-oriented EF"""
728 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100729 if data:
730 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100731
732 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100733 upd_rec_dec_parser.add_argument(
734 'record_nr', type=int, help='Number of record to be read')
735 upd_rec_dec_parser.add_argument(
736 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200737 upd_rec_dec_parser.add_argument('--json-path', type=str,
738 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100739
Harald Welteb2edd142021-01-08 23:29:35 +0100740 @cmd2.with_argparser(upd_rec_dec_parser)
741 def do_update_record_decoded(self, opts):
742 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200743 if opts.json_path:
744 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100745 js_path_modify(data_json, opts.json_path,
746 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200747 else:
748 data_json = json.loads(opts.data)
Harald Weltec91085e2022-02-10 18:05:45 +0100749 (data, sw) = self._cmd.rs.update_record_dec(
750 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100751 if data:
752 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100753
Harald Welte4145d3c2021-04-08 20:34:13 +0200754 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100755 edit_rec_dec_parser.add_argument(
756 'record_nr', type=int, help='Number of record to be edited')
757
Harald Welte4145d3c2021-04-08 20:34:13 +0200758 @cmd2.with_argparser(edit_rec_dec_parser)
759 def do_edit_record_decoded(self, opts):
760 """Edit the JSON representation of one record in an editor."""
761 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200762 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200763 filename = '%s/file' % dirname
764 # write existing data as JSON to file
765 with open(filename, 'w') as text_file:
766 json.dump(orig_json, text_file, indent=4)
767 # run a text editor
768 self._cmd._run_editor(filename)
769 with open(filename, 'r') as text_file:
770 edited_json = json.load(text_file)
771 if edited_json == orig_json:
772 self._cmd.poutput("Data not modified, skipping write")
773 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100774 (data, sw) = self._cmd.rs.update_record_dec(
775 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200776 if data:
777 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200778
Harald Weltec91085e2022-02-10 18:05:45 +0100779 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
780 parent: Optional[CardDF] = None, rec_len={1, None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200781 """
782 Args:
783 fid : File Identifier (4 hex digits)
784 sfid : Short File Identifier (2 hex digits, optional)
785 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200786 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200787 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200788 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200789 """
Harald Welteb2edd142021-01-08 23:29:35 +0100790 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
791 self.rec_len = rec_len
792 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200793 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200794 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100795
Harald Weltec91085e2022-02-10 18:05:45 +0100796 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200797 """Decode raw (hex string) data into abstract representation.
798
799 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
800 method for implementing this specifically for the given file. This function checks which
801 of the method exists, add calls them (with conversion, as needed).
802
803 Args:
804 raw_hex_data : hex-encoded data
805 Returns:
806 abstract_data; dict representing the decoded data
807 """
Harald Welteb2edd142021-01-08 23:29:35 +0100808 method = getattr(self, '_decode_record_hex', None)
809 if callable(method):
810 return method(raw_hex_data)
811 raw_bin_data = h2b(raw_hex_data)
812 method = getattr(self, '_decode_record_bin', None)
813 if callable(method):
814 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200815 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200816 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200817 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100818 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100819 t.from_tlv(raw_bin_data)
820 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100821 return {'raw': raw_bin_data.hex()}
822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200824 """Decode raw (binary) data into abstract representation.
825
826 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
827 method for implementing this specifically for the given file. This function checks which
828 of the method exists, add calls them (with conversion, as needed).
829
830 Args:
831 raw_bin_data : binary encoded data
832 Returns:
833 abstract_data; dict representing the decoded data
834 """
Harald Welteb2edd142021-01-08 23:29:35 +0100835 method = getattr(self, '_decode_record_bin', None)
836 if callable(method):
837 return method(raw_bin_data)
838 raw_hex_data = b2h(raw_bin_data)
839 method = getattr(self, '_decode_record_hex', None)
840 if callable(method):
841 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200842 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200843 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200844 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100845 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100846 t.from_tlv(raw_bin_data)
847 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100848 return {'raw': raw_hex_data}
849
Harald Weltec91085e2022-02-10 18:05:45 +0100850 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200851 """Encode abstract representation into raw (hex string) data.
852
853 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
854 method for implementing this specifically for the given file. This function checks which
855 of the method exists, add calls them (with conversion, as needed).
856
857 Args:
858 abstract_data : dict representing the decoded data
859 Returns:
860 hex string encoded data
861 """
Harald Welteb2edd142021-01-08 23:29:35 +0100862 method = getattr(self, '_encode_record_hex', None)
863 if callable(method):
864 return method(abstract_data)
865 method = getattr(self, '_encode_record_bin', None)
866 if callable(method):
867 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200868 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200869 if self._construct:
870 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200871 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100872 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100873 t.from_dict(abstract_data)
874 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100875 raise NotImplementedError(
876 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100877
Harald Weltec91085e2022-02-10 18:05:45 +0100878 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200879 """Encode abstract representation into raw (binary) data.
880
881 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
882 method for implementing this specifically for the given file. This function checks which
883 of the method exists, add calls them (with conversion, as needed).
884
885 Args:
886 abstract_data : dict representing the decoded data
887 Returns:
888 binary encoded data
889 """
Harald Welteb2edd142021-01-08 23:29:35 +0100890 method = getattr(self, '_encode_record_bin', None)
891 if callable(method):
892 return method(abstract_data)
893 method = getattr(self, '_encode_record_hex', None)
894 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200895 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200896 if self._construct:
897 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200898 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100899 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100900 t.from_dict(abstract_data)
901 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100902 raise NotImplementedError(
903 "%s encoder not yet implemented. Patches welcome." % self)
904
Harald Welteb2edd142021-01-08 23:29:35 +0100905
906class CyclicEF(LinFixedEF):
907 """Cyclic EF (Entry File) in the smart card filesystem"""
908 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +0100909
910 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
911 rec_len={1, None}):
912 super().__init__(fid=fid, sfid=sfid, name=name,
913 desc=desc, parent=parent, rec_len=rec_len)
914
Harald Welteb2edd142021-01-08 23:29:35 +0100915
916class TransRecEF(TransparentEF):
917 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200918
Harald Welteb2edd142021-01-08 23:29:35 +0100919 These are the real odd-balls and mostly look like mistakes in the specification:
920 Specified as 'transparent' EF, but actually containing several fixed-length records
921 inside.
922 We add a special class for those, so the user only has to provide encoder/decoder functions
923 for a record, while this class takes care of split / merge of records.
924 """
Harald Weltec91085e2022-02-10 18:05:45 +0100925
926 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
927 parent: Optional[CardDF] = None, size={1, None}):
Harald Welteee3501f2021-04-02 13:00:18 +0200928 """
929 Args:
930 fid : File Identifier (4 hex digits)
931 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200932 name : Brief name of the file, like EF_ICCID
933 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200934 parent : Parent CardFile object within filesystem hierarchy
935 rec_len : Length of the fixed-length records within transparent EF
936 size : tuple of (minimum_size, recommended_size)
937 """
Harald Welteb2edd142021-01-08 23:29:35 +0100938 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
939 self.rec_len = rec_len
940
Harald Weltec91085e2022-02-10 18:05:45 +0100941 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200942 """Decode raw (hex string) data into abstract representation.
943
944 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
945 method for implementing this specifically for the given file. This function checks which
946 of the method exists, add calls them (with conversion, as needed).
947
948 Args:
949 raw_hex_data : hex-encoded data
950 Returns:
951 abstract_data; dict representing the decoded data
952 """
Harald Welteb2edd142021-01-08 23:29:35 +0100953 method = getattr(self, '_decode_record_hex', None)
954 if callable(method):
955 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200956 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +0100957 method = getattr(self, '_decode_record_bin', None)
958 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +0100959 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200960 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200961 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200962 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100963 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100964 t.from_tlv(raw_bin_data)
965 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100966 return {'raw': raw_hex_data}
967
Harald Weltec91085e2022-02-10 18:05:45 +0100968 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200969 """Decode raw (binary) data into abstract representation.
970
971 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
972 method for implementing this specifically for the given file. This function checks which
973 of the method exists, add calls them (with conversion, as needed).
974
975 Args:
976 raw_bin_data : binary encoded data
977 Returns:
978 abstract_data; dict representing the decoded data
979 """
Harald Welteb2edd142021-01-08 23:29:35 +0100980 method = getattr(self, '_decode_record_bin', None)
981 if callable(method):
982 return method(raw_bin_data)
983 raw_hex_data = b2h(raw_bin_data)
984 method = getattr(self, '_decode_record_hex', None)
985 if callable(method):
986 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200987 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200988 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200989 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100990 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100991 t.from_tlv(raw_bin_data)
992 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100993 return {'raw': raw_hex_data}
994
Harald Weltec91085e2022-02-10 18:05:45 +0100995 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200996 """Encode abstract representation into raw (hex string) data.
997
998 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
999 method for implementing this specifically for the given file. This function checks which
1000 of the method exists, add calls them (with conversion, as needed).
1001
1002 Args:
1003 abstract_data : dict representing the decoded data
1004 Returns:
1005 hex string encoded data
1006 """
Harald Welteb2edd142021-01-08 23:29:35 +01001007 method = getattr(self, '_encode_record_hex', None)
1008 if callable(method):
1009 return method(abstract_data)
1010 method = getattr(self, '_encode_record_bin', None)
1011 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001012 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001013 if self._construct:
1014 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001015 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001016 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001017 t.from_dict(abstract_data)
1018 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001019 raise NotImplementedError(
1020 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001021
Harald Weltec91085e2022-02-10 18:05:45 +01001022 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001023 """Encode abstract representation into raw (binary) data.
1024
1025 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1026 method for implementing this specifically for the given file. This function checks which
1027 of the method exists, add calls them (with conversion, as needed).
1028
1029 Args:
1030 abstract_data : dict representing the decoded data
1031 Returns:
1032 binary encoded data
1033 """
Harald Welteb2edd142021-01-08 23:29:35 +01001034 method = getattr(self, '_encode_record_bin', None)
1035 if callable(method):
1036 return method(abstract_data)
1037 method = getattr(self, '_encode_record_hex', None)
1038 if callable(method):
1039 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001040 if self._construct:
1041 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001042 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001043 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001044 t.from_dict(abstract_data)
1045 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001046 raise NotImplementedError(
1047 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001048
Harald Weltec91085e2022-02-10 18:05:45 +01001049 def _decode_bin(self, raw_bin_data: bytearray):
1050 chunks = [raw_bin_data[i:i+self.rec_len]
1051 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001052 return [self.decode_record_bin(x) for x in chunks]
1053
Harald Welteee3501f2021-04-02 13:00:18 +02001054 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001055 chunks = [self.encode_record_bin(x) for x in abstract_data]
1056 # FIXME: pad to file size
1057 return b''.join(chunks)
1058
1059
Harald Welte917d98c2021-04-21 11:51:25 +02001060class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001061 """BER-TLV EF (Entry File) in the smart card filesystem.
1062 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001063
Harald Welte27881622021-04-21 11:16:31 +02001064 NOTE: We currently don't really support those, this class is simply a wrapper
1065 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1066 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001067
Harald Welte917d98c2021-04-21 11:51:25 +02001068 @with_default_category('BER-TLV EF Commands')
1069 class ShellCommands(CommandSet):
1070 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001071
Harald Welte917d98c2021-04-21 11:51:25 +02001072 def __init__(self):
1073 super().__init__()
1074
1075 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001076 retrieve_data_parser.add_argument(
1077 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1078
Harald Welte917d98c2021-04-21 11:51:25 +02001079 @cmd2.with_argparser(retrieve_data_parser)
1080 def do_retrieve_data(self, opts):
1081 """Retrieve (Read) data from a BER-TLV EF"""
1082 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1083 self._cmd.poutput(data)
1084
1085 def do_retrieve_tags(self, opts):
1086 """List tags available in a given BER-TLV EF"""
1087 tags = self._cmd.rs.retrieve_tags()
1088 self._cmd.poutput(tags)
1089
1090 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001091 set_data_parser.add_argument(
1092 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1093 set_data_parser.add_argument(
1094 'data', help='Data bytes (hex format) to write')
1095
Harald Welte917d98c2021-04-21 11:51:25 +02001096 @cmd2.with_argparser(set_data_parser)
1097 def do_set_data(self, opts):
1098 """Set (Write) data for a given tag in a BER-TLV EF"""
1099 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1100 if data:
1101 self._cmd.poutput(data)
1102
1103 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001104 del_data_parser.add_argument(
1105 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1106
Harald Welte917d98c2021-04-21 11:51:25 +02001107 @cmd2.with_argparser(del_data_parser)
1108 def do_delete_data(self, opts):
1109 """Delete data for a given tag in a BER-TLV EF"""
1110 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1111 if data:
1112 self._cmd.poutput(data)
1113
Harald Weltec91085e2022-02-10 18:05:45 +01001114 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
1115 size={1, None}):
Harald Welte917d98c2021-04-21 11:51:25 +02001116 """
1117 Args:
1118 fid : File Identifier (4 hex digits)
1119 sfid : Short File Identifier (2 hex digits, optional)
1120 name : Brief name of the file, lik EF_ICCID
1121 desc : Description of the file
1122 parent : Parent CardFile object within filesystem hierarchy
1123 size : tuple of (minimum_size, recommended_size)
1124 """
1125 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent)
1126 self._construct = None
1127 self.size = size
1128 self.shell_commands = [self.ShellCommands()]
1129
Harald Welteb2edd142021-01-08 23:29:35 +01001130
1131class RuntimeState(object):
1132 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001133
1134 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001135 """
1136 Args:
1137 card : pysim.cards.Card instance
1138 profile : CardProfile instance
1139 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001140 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001141 self.card = card
Harald Weltec91085e2022-02-10 18:05:45 +01001142 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001143 self.profile = profile
Philipp Maier51cad0d2021-11-08 15:45:10 +01001144
1145 # make sure the class and selection control bytes, which are specified
1146 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001147 self.card.set_apdu_parameter(
1148 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001149
Harald Welte5ce35242021-04-02 20:27:05 +02001150 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001151 apps = self._match_applications()
1152 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001153 if a.adf:
1154 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001155 for f in self.profile.files_in_mf:
1156 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001157 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001158
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001159 # make sure that when the runtime state is created, the card is also
1160 # in a defined state.
1161 self.reset()
1162
Philipp Maier1e896f32021-03-10 17:02:53 +01001163 def _match_applications(self):
1164 """match the applications from the profile with applications on the card"""
1165 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001166
1167 # When the profile does not feature any applications, then we are done already
1168 if not apps_profile:
1169 return []
1170
1171 # Read AIDs from card and match them against the applications defined by the
1172 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001173 aids_card = self.card.read_aids()
1174 apps_taken = []
1175 if aids_card:
1176 aids_taken = []
1177 print("AIDs on card:")
1178 for a in aids_card:
1179 for f in apps_profile:
1180 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001181 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001182 aids_taken.append(a)
1183 apps_taken.append(f)
1184 aids_unknown = set(aids_card) - set(aids_taken)
1185 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001186 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001187 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001188 print("warning: EF.DIR seems to be empty!")
1189
1190 # Some card applications may not be registered in EF.DIR, we will actively
1191 # probe for those applications
1192 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001193 try:
1194 data, sw = self.card.select_adf_by_aid(f.aid)
1195 if sw == "9000":
1196 print(" %s: %s" % (f.name, f.aid))
1197 apps_taken.append(f)
1198 except SwMatchError:
1199 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001200 return apps_taken
1201
Harald Weltedaf2b392021-05-03 23:17:29 +02001202 def reset(self, cmd_app=None) -> Hexstr:
1203 """Perform physical card reset and obtain ATR.
1204 Args:
1205 cmd_app : Command Application State (for unregistering old file commands)
1206 """
Philipp Maier946226a2021-10-29 18:31:03 +02001207 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001208 # select MF to reset internal state and to verify card really works
1209 self.select('MF', cmd_app)
1210 return atr
1211
Harald Welteee3501f2021-04-02 13:00:18 +02001212 def get_cwd(self) -> CardDF:
1213 """Obtain the current working directory.
1214
1215 Returns:
1216 CardDF instance
1217 """
Harald Welteb2edd142021-01-08 23:29:35 +01001218 if isinstance(self.selected_file, CardDF):
1219 return self.selected_file
1220 else:
1221 return self.selected_file.parent
1222
Harald Welte5ce35242021-04-02 20:27:05 +02001223 def get_application_df(self) -> Optional[CardADF]:
1224 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001225
1226 Returns:
1227 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001228 # iterate upwards from selected file; check if any is an ADF
1229 node = self.selected_file
1230 while node.parent != node:
1231 if isinstance(node, CardADF):
1232 return node
1233 node = node.parent
1234 return None
1235
Harald Weltec91085e2022-02-10 18:05:45 +01001236 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001237 """Interpret a given status word relative to the currently selected application
1238 or the underlying card profile.
1239
1240 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001241 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001242
1243 Returns:
1244 Tuple of two strings
1245 """
Harald Welte86fbd392021-04-02 22:13:09 +02001246 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001247 adf = self.get_application_df()
1248 if adf:
1249 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001250 # The application either comes with its own interpret_sw
1251 # method or we will use the interpret_sw method from the
1252 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001253 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001254 res = app.interpret_sw(sw)
1255 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001256
Harald Weltec91085e2022-02-10 18:05:45 +01001257 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001258 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001259 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001260 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001261 raise ValueError(
1262 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001263
1264 try:
1265 (data, sw) = self.card._scc.select_file(fid)
1266 except SwMatchError as swm:
1267 k = self.interpret_sw(swm.sw_actual)
1268 if not k:
1269 raise(swm)
1270 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1271
1272 select_resp = self.selected_file.decode_select_response(data)
1273 if (select_resp['file_descriptor']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001274 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1275 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001276 else:
1277 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001278 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1279 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001280 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001281 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1282 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001283
1284 self.selected_file.add_files([f])
1285 self.selected_file = f
1286 return select_resp
1287
Harald Weltec91085e2022-02-10 18:05:45 +01001288 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001289 """Select a file (EF, DF, ADF, MF, ...).
1290
1291 Args:
1292 name : Name of file to select
1293 cmd_app : Command Application State (for unregistering old file commands)
1294 """
Harald Welteb2edd142021-01-08 23:29:35 +01001295 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001296 if is_hex(name):
1297 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001298
1299 # unregister commands of old file
1300 if cmd_app and self.selected_file.shell_commands:
1301 for c in self.selected_file.shell_commands:
1302 cmd_app.unregister_command_set(c)
1303
Harald Welteb2edd142021-01-08 23:29:35 +01001304 if name in sels:
1305 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001306 try:
1307 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001308 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001309 else:
1310 (data, sw) = self.card._scc.select_file(f.fid)
1311 self.selected_file = f
1312 except SwMatchError as swm:
1313 k = self.interpret_sw(swm.sw_actual)
1314 if not k:
1315 raise(swm)
1316 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001317 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001318 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001319 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001320 # store the decoded FCP for later reference
1321 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001322
1323 # register commands of new file
1324 if cmd_app and self.selected_file.shell_commands:
1325 for c in self.selected_file.shell_commands:
1326 cmd_app.register_command_set(c)
1327
1328 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001329
Harald Welte34b05d32021-05-25 22:03:13 +02001330 def status(self):
1331 """Request STATUS (current selected file FCP) from card."""
1332 (data, sw) = self.card._scc.status()
1333 return self.selected_file.decode_select_response(data)
1334
Harald Weltec91085e2022-02-10 18:05:45 +01001335 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001336 """Request ACTIVATE FILE of specified file."""
1337 sels = self.selected_file.get_selectables()
1338 f = sels[name]
1339 data, sw = self.card._scc.activate_file(f.fid)
1340 return data, sw
1341
Harald Weltec91085e2022-02-10 18:05:45 +01001342 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001343 """Read [part of] a transparent EF binary data.
1344
1345 Args:
1346 length : Amount of data to read (None: as much as possible)
1347 offset : Offset into the file from which to read 'length' bytes
1348 Returns:
1349 binary data read from the file
1350 """
Harald Welteb2edd142021-01-08 23:29:35 +01001351 if not isinstance(self.selected_file, TransparentEF):
1352 raise TypeError("Only works with TransparentEF")
1353 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1354
Harald Welte2d4a64b2021-04-03 09:01:24 +02001355 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001356 """Read [part of] a transparent EF binary data and decode it.
1357
1358 Args:
1359 length : Amount of data to read (None: as much as possible)
1360 offset : Offset into the file from which to read 'length' bytes
1361 Returns:
1362 abstract decode data read from the file
1363 """
Harald Welteb2edd142021-01-08 23:29:35 +01001364 (data, sw) = self.read_binary()
1365 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001366 return (dec_data, sw)
1367
Harald Weltec91085e2022-02-10 18:05:45 +01001368 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001369 """Update transparent EF binary data.
1370
1371 Args:
1372 data_hex : hex string of data to be written
1373 offset : Offset into the file from which to write 'data_hex'
1374 """
Harald Welteb2edd142021-01-08 23:29:35 +01001375 if not isinstance(self.selected_file, TransparentEF):
1376 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001377 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001378
Harald Weltec91085e2022-02-10 18:05:45 +01001379 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001380 """Update transparent EF from abstract data. Encodes the data to binary and
1381 then updates the EF with it.
1382
1383 Args:
1384 data : abstract data which is to be encoded and written
1385 """
Harald Welteb2edd142021-01-08 23:29:35 +01001386 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001387 return self.update_binary(data_hex)
1388
Harald Weltec91085e2022-02-10 18:05:45 +01001389 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001390 """Read a record as binary data.
1391
1392 Args:
1393 rec_nr : Record number to read
1394 Returns:
1395 hex string of binary data contained in record
1396 """
Harald Welteb2edd142021-01-08 23:29:35 +01001397 if not isinstance(self.selected_file, LinFixedEF):
1398 raise TypeError("Only works with Linear Fixed EF")
1399 # returns a string of hex nibbles
1400 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1401
Harald Weltec91085e2022-02-10 18:05:45 +01001402 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001403 """Read a record and decode it to abstract data.
1404
1405 Args:
1406 rec_nr : Record number to read
1407 Returns:
1408 abstract data contained in record
1409 """
Harald Welteb2edd142021-01-08 23:29:35 +01001410 (data, sw) = self.read_record(rec_nr)
1411 return (self.selected_file.decode_record_hex(data), sw)
1412
Harald Weltec91085e2022-02-10 18:05:45 +01001413 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001414 """Update a record with given binary data
1415
1416 Args:
1417 rec_nr : Record number to read
1418 data_hex : Hex string binary data to be written
1419 """
Harald Welteb2edd142021-01-08 23:29:35 +01001420 if not isinstance(self.selected_file, LinFixedEF):
1421 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001422 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 +01001423
Harald Weltec91085e2022-02-10 18:05:45 +01001424 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001425 """Update a record with given abstract data. Will encode abstract to binary data
1426 and then write it to the given record on the card.
1427
1428 Args:
1429 rec_nr : Record number to read
1430 data_hex : Abstract data to be written
1431 """
Harald Welte1e456572021-04-02 17:16:30 +02001432 data_hex = self.selected_file.encode_record_hex(data)
1433 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001434
Harald Weltec91085e2022-02-10 18:05:45 +01001435 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001436 """Read a DO/TLV as binary data.
1437
1438 Args:
1439 tag : Tag of TLV/DO to read
1440 Returns:
1441 hex string of full BER-TLV DO including Tag and Length
1442 """
1443 if not isinstance(self.selected_file, BerTlvEF):
1444 raise TypeError("Only works with BER-TLV EF")
1445 # returns a string of hex nibbles
1446 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1447
1448 def retrieve_tags(self):
1449 """Retrieve tags available on BER-TLV EF.
1450
1451 Returns:
1452 list of integer tags contained in EF
1453 """
1454 if not isinstance(self.selected_file, BerTlvEF):
1455 raise TypeError("Only works with BER-TLV EF")
1456 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001457 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001458 return list(value)
1459
Harald Weltec91085e2022-02-10 18:05:45 +01001460 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001461 """Update a TLV/DO with given binary data
1462
1463 Args:
1464 tag : Tag of TLV/DO to be written
1465 data_hex : Hex string binary data to be written (value portion)
1466 """
1467 if not isinstance(self.selected_file, BerTlvEF):
1468 raise TypeError("Only works with BER-TLV EF")
1469 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1470
Philipp Maier5d698e52021-09-16 13:18:01 +02001471 def unregister_cmds(self, cmd_app=None):
1472 """Unregister all file specific commands."""
1473 if cmd_app and self.selected_file.shell_commands:
1474 for c in self.selected_file.shell_commands:
1475 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001476
Harald Welteb2edd142021-01-08 23:29:35 +01001477
Harald Welteb2edd142021-01-08 23:29:35 +01001478class FileData(object):
1479 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001480
Harald Welteb2edd142021-01-08 23:29:35 +01001481 def __init__(self, fdesc):
1482 self.desc = fdesc
1483 self.fcp = None
1484
1485
Harald Weltec91085e2022-02-10 18:05:45 +01001486def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001487 """Interpret a given status word.
1488
1489 Args:
1490 sw_data : Hierarchical dict of status word matches
1491 sw : status word to match (string of 4 hex digits)
1492 Returns:
1493 tuple of two strings (class_string, description)
1494 """
Harald Welteb2edd142021-01-08 23:29:35 +01001495 for class_str, swdict in sw_data.items():
1496 # first try direct match
1497 if sw in swdict:
1498 return (class_str, swdict[sw])
1499 # next try wildcard matches
1500 for pattern, descr in swdict.items():
1501 if sw_match(sw, pattern):
1502 return (class_str, descr)
1503 return None
1504
Harald Weltec91085e2022-02-10 18:05:45 +01001505
Harald Welteb2edd142021-01-08 23:29:35 +01001506class CardApplication(object):
1507 """A card application is represented by an ADF (with contained hierarchy) and optionally
1508 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001509
1510 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001511 """
1512 Args:
1513 adf : ADF name
1514 sw : Dict of status word conversions
1515 """
Harald Welteb2edd142021-01-08 23:29:35 +01001516 self.name = name
1517 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001518 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001519 # back-reference from ADF to Applicaiton
1520 if self.adf:
1521 self.aid = aid or self.adf.aid
1522 self.adf.application = self
1523 else:
1524 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001525
1526 def __str__(self):
1527 return "APP(%s)" % (self.name)
1528
1529 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001530 """Interpret a given status word within the application.
1531
1532 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001533 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001534
1535 Returns:
1536 Tuple of two strings
1537 """
Harald Welteb2edd142021-01-08 23:29:35 +01001538 return interpret_sw(self.sw, sw)
1539
Harald Weltef44256c2021-10-14 15:53:39 +02001540
1541class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001542 """A specific card model, typically having some additional vendor-specific files. All
1543 you need to do is to define a sub-class with a list of ATRs or an overridden match
1544 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001545 _atrs = []
1546
1547 @classmethod
1548 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001549 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001550 """Add model specific files to given RuntimeState."""
1551
1552 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001553 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001554 """Test if given card matches this model."""
1555 card_atr = scc.get_atr()
1556 for atr in cls._atrs:
1557 atr_bin = toBytes(atr)
1558 if atr_bin == card_atr:
1559 print("Detected CardModel:", cls.__name__)
1560 return True
1561 return False
1562
1563 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001564 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001565 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1566 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1567 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001568 for m in CardModel.__subclasses__():
1569 if m.match(scc):
1570 m.add_files(rs)