blob: dd80071b7912f18d877e494b76f5251e52823f0a [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
Harald Welte9170fbf2022-02-11 21:54:37 +010037from typing import cast, Optional, Iterable, List, Dict, Tuple, Union
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 Welte9170fbf2022-02-11 21:54:37 +010047# int: a single service is associated with this file
48# list: any of the listed services requires this file
49# tuple: logical-and of the listed services requires this file
50CardFileService = Union[int, List[int], Tuple[int, ...]]
Harald Weltec91085e2022-02-10 18:05:45 +010051
Harald Welteb2edd142021-01-08 23:29:35 +010052class CardFile(object):
53 """Base class for all objects in the smart card filesystem.
54 Serve as a common ancestor to all other file types; rarely used directly.
55 """
56 RESERVED_NAMES = ['..', '.', '/', 'MF']
57 RESERVED_FIDS = ['3f00']
58
Harald Weltec91085e2022-02-10 18:05:45 +010059 def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +010060 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None,
61 service: Optional[CardFileService] = None):
Harald Welteee3501f2021-04-02 13:00:18 +020062 """
63 Args:
64 fid : File Identifier (4 hex digits)
65 sfid : Short File Identifier (2 hex digits, optional)
66 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020067 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020068 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier5af7bdf2021-11-04 12:48:41 +010069 profile : Card profile that this file should be part of
Harald Welte9170fbf2022-02-11 21:54:37 +010070 service : Service (SST/UST/IST) associated with the file
Harald Welteee3501f2021-04-02 13:00:18 +020071 """
Harald Welteb2edd142021-01-08 23:29:35 +010072 if not isinstance(self, CardADF) and fid == None:
73 raise ValueError("fid is mandatory")
74 if fid:
75 fid = fid.lower()
76 self.fid = fid # file identifier
77 self.sfid = sfid # short file identifier
78 self.name = name # human readable name
79 self.desc = desc # human readable description
80 self.parent = parent
81 if self.parent and self.parent != self and self.fid:
82 self.parent.add_file(self)
Philipp Maier5af7bdf2021-11-04 12:48:41 +010083 self.profile = profile
Harald Welte9170fbf2022-02-11 21:54:37 +010084 self.service = service
Harald Weltec91085e2022-02-10 18:05:45 +010085 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +010086
Harald Weltec91085e2022-02-10 18:05:45 +010087 # Note: the basic properties (fid, name, ect.) are verified when
88 # the file is attached to a parent file. See method add_file() in
89 # class Card DF
Philipp Maier66061582021-03-09 21:57:57 +010090
Harald Welteb2edd142021-01-08 23:29:35 +010091 def __str__(self):
92 if self.name:
93 return self.name
94 else:
95 return self.fid
96
Harald Weltec91085e2022-02-10 18:05:45 +010097 def _path_element(self, prefer_name: bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +010098 if prefer_name and self.name:
99 return self.name
100 else:
101 return self.fid
102
Harald Weltec91085e2022-02-10 18:05:45 +0100103 def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200104 """Return fully qualified path to file as list of FID or name strings.
105
106 Args:
107 prefer_name : Preferably build path of names; fall-back to FIDs as required
108 """
Harald Welte1e456572021-04-02 17:16:30 +0200109 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +0100110 ret = self.parent.fully_qualified_path(prefer_name)
111 else:
112 ret = []
Harald Welte1e456572021-04-02 17:16:30 +0200113 elem = self._path_element(prefer_name)
114 if elem:
115 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100116 return ret
117
Harald Welteee3501f2021-04-02 13:00:18 +0200118 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100119 """Return the MF (root) of the file system."""
120 if self.parent == None:
121 return None
122 # iterate towards the top. MF has parent == self
123 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200124 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100125 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200126 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100127
Harald Weltec91085e2022-02-10 18:05:45 +0100128 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200129 """Return a dict of {'identifier': self} tuples.
130
131 Args:
132 alias : Add an alias with given name to 'self'
133 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
134 If not specified, all selectables will be returned.
135 Returns:
136 dict containing reference to 'self' for all identifiers.
137 """
Harald Welteb2edd142021-01-08 23:29:35 +0100138 sels = {}
139 if alias:
140 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100141 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100142 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100143 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100144 sels.update({self.name: self})
145 return sels
146
Harald Weltec91085e2022-02-10 18:05:45 +0100147 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200148 """Return a dict of {'identifier': File} that is selectable from the current file.
149
150 Args:
151 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
152 If not specified, all selectables will be returned.
153 Returns:
154 dict containing all selectable items. Key is identifier (string), value
155 a reference to a CardFile (or derived class) instance.
156 """
Philipp Maier786f7812021-02-25 16:48:10 +0100157 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100158 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100159 if flags == [] or 'SELF' in flags:
160 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100161 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100162 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200163 if self.parent:
164 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100165 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100166 if flags == [] or 'MF' in flags:
167 mf = self.get_mf()
168 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100169 sels.update(mf._get_self_selectables(flags=flags))
170 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100171 return sels
172
Harald Weltec91085e2022-02-10 18:05:45 +0100173 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200174 """Return a dict of {'identifier': File} that is selectable from the current file.
175
176 Args:
177 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
178 If not specified, all selectables will be returned.
179 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200180 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200181 """
Philipp Maier786f7812021-02-25 16:48:10 +0100182 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100183 sel_keys = list(sels.keys())
184 sel_keys.sort()
185 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100186
Harald Weltec91085e2022-02-10 18:05:45 +0100187 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100188 """Decode the response to a SELECT command.
189
190 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100191 data_hex: Hex string of the select response
192 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100193
Harald Weltec91085e2022-02-10 18:05:45 +0100194 # When the current file does not implement a custom select response decoder,
195 # we just ask the parent file to decode the select response. If this method
196 # is not overloaded by the current file we will again ask the parent file.
197 # This way we recursively travel up the file system tree until we hit a file
198 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200199 if self.parent:
200 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100201
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100202 def get_profile(self):
203 """Get the profile associated with this file. If this file does not have any
204 profile assigned, try to find a file above (usually the MF) in the filesystem
205 hirarchy that has a profile assigned
206 """
207
208 # If we have a profile set, return it
209 if self.profile:
210 return self.profile
211
212 # Walk up recursively until we hit a parent that has a profile set
213 if self.parent:
214 return self.parent.get_profile()
215 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100216
Harald Welte9170fbf2022-02-11 21:54:37 +0100217 def should_exist_for_services(self, services: List[int]):
218 """Assuming the provided list of activated services, should this file exist and be activated?."""
219 if self.service is None:
220 return None
221 elif isinstance(self.service, int):
222 # a single service determines the result
223 return self.service in services
224 elif isinstance(self.service, list):
225 # any of the services active -> true
226 for s in self.service:
227 if s in services:
228 return True
229 return False
230 elif isinstance(self.service, tuple):
231 # all of the services active -> true
232 for s in self.service:
233 if not s in services:
234 return False
235 return True
236 else:
237 raise ValueError("self.service must be either int or list or tuple")
238
Harald Weltec91085e2022-02-10 18:05:45 +0100239
Harald Welteb2edd142021-01-08 23:29:35 +0100240class CardDF(CardFile):
241 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100242
243 @with_default_category('DF/ADF Commands')
244 class ShellCommands(CommandSet):
245 def __init__(self):
246 super().__init__()
247
Harald Welteb2edd142021-01-08 23:29:35 +0100248 def __init__(self, **kwargs):
249 if not isinstance(self, CardADF):
250 if not 'fid' in kwargs:
251 raise TypeError('fid is mandatory for all DF')
252 super().__init__(**kwargs)
253 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100254 self.shell_commands = [self.ShellCommands()]
Harald Welte9170fbf2022-02-11 21:54:37 +0100255 # dict of CardFile affected by service(int), indexed by service
256 self.files_by_service = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100257
258 def __str__(self):
259 return "DF(%s)" % (super().__str__())
260
Harald Welte9170fbf2022-02-11 21:54:37 +0100261 def _add_file_services(self, child):
262 """Add a child (DF/EF) to the files_by_services of the parent."""
263 if not child.service:
264 return
265 if isinstance(child.service, int):
266 self.files_by_service.setdefault(child.service, []).append(child)
267 elif isinstance(child.service, list):
268 for service in child.service:
269 self.files_by_service.setdefault(service, []).append(child)
270 elif isinstance(child.service, tuple):
271 for service in child.service:
272 self.files_by_service.setdefault(service, []).append(child)
273 else:
274 raise ValueError
275
Harald Weltec91085e2022-02-10 18:05:45 +0100276 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200277 """Add a child (DF/EF) to this DF.
278 Args:
279 child: The new DF/EF to be added
280 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
281 """
Harald Welteb2edd142021-01-08 23:29:35 +0100282 if not isinstance(child, CardFile):
283 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100284 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100285 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100286 if child.name in CardFile.RESERVED_NAMES:
287 raise ValueError("File name %s is a reserved name" % (child.name))
288 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100289 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100290 if child.fid in self.children:
291 if ignore_existing:
292 return
Harald Weltec91085e2022-02-10 18:05:45 +0100293 raise ValueError(
294 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100295 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100296 raise ValueError(
297 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100298 if self.lookup_file_by_name(child.name):
299 if ignore_existing:
300 return
Harald Weltec91085e2022-02-10 18:05:45 +0100301 raise ValueError(
302 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100303 self.children[child.fid] = child
304 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100305 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100306 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100307 if isinstance(child, CardDF):
308 for c in child.children.values():
309 self._add_file_services(c)
310 if isinstance(c, CardDF):
311 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100312
Harald Weltec91085e2022-02-10 18:05:45 +0100313 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200314 """Add a list of child (DF/EF) to this DF
315
316 Args:
317 children: List of new DF/EFs to be added
318 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
319 """
Harald Welteb2edd142021-01-08 23:29:35 +0100320 for child in children:
321 self.add_file(child, ignore_existing)
322
Harald Weltec91085e2022-02-10 18:05:45 +0100323 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200324 """Return a dict of {'identifier': File} that is selectable from the current DF.
325
326 Args:
327 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
328 If not specified, all selectables will be returned.
329 Returns:
330 dict containing all selectable items. Key is identifier (string), value
331 a reference to a CardFile (or derived class) instance.
332 """
Harald Welteb2edd142021-01-08 23:29:35 +0100333 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100334 sels = super().get_selectables(flags)
335 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100336 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100337 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100338 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100339 return sels
340
Harald Weltec91085e2022-02-10 18:05:45 +0100341 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200342 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100343 if name == None:
344 return None
345 for i in self.children.values():
346 if i.name and i.name == name:
347 return i
348 return None
349
Harald Weltec91085e2022-02-10 18:05:45 +0100350 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200351 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100352 if sfid == None:
353 return None
354 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200355 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100356 return i
357 return None
358
Harald Weltec91085e2022-02-10 18:05:45 +0100359 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200360 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100361 if fid in self.children:
362 return self.children[fid]
363 return None
364
365
366class CardMF(CardDF):
367 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100368
Harald Welteb2edd142021-01-08 23:29:35 +0100369 def __init__(self, **kwargs):
370 # can be overridden; use setdefault
371 kwargs.setdefault('fid', '3f00')
372 kwargs.setdefault('name', 'MF')
373 kwargs.setdefault('desc', 'Master File (directory root)')
374 # cannot be overridden; use assignment
375 kwargs['parent'] = self
376 super().__init__(**kwargs)
377 self.applications = dict()
378
379 def __str__(self):
380 return "MF(%s)" % (self.fid)
381
Harald Weltec91085e2022-02-10 18:05:45 +0100382 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200383 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100384 if not isinstance(app, CardADF):
385 raise TypeError("Expected an ADF instance")
386 if app.aid in self.applications:
387 raise ValueError("AID %s already exists" % (app.aid))
388 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100389 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100390
391 def get_app_names(self):
392 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100393 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100394
Harald Weltec91085e2022-02-10 18:05:45 +0100395 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200396 """Return a dict of {'identifier': File} that is selectable from the current DF.
397
398 Args:
399 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
400 If not specified, all selectables will be returned.
401 Returns:
402 dict containing all selectable items. Key is identifier (string), value
403 a reference to a CardFile (or derived class) instance.
404 """
Philipp Maier786f7812021-02-25 16:48:10 +0100405 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100406 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100407 return sels
408
Harald Weltec91085e2022-02-10 18:05:45 +0100409 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100410 """Get applications by AID + name"""
411 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100412 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100413 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100414 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100415 sels.update(
416 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100417 return sels
418
Harald Weltec9752512022-02-11 16:31:15 +0100419 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200420 """Decode the response to a SELECT command.
421
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100422 This is the fall-back method which automatically defers to the standard decoding
423 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100424 performed. Specific derived classes (usually ADF) can overload this method to
425 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200426 """
Harald Welteb2edd142021-01-08 23:29:35 +0100427
Harald Weltec9752512022-02-11 16:31:15 +0100428 if not data_hex:
429 return data_hex
430
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100431 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100432
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100433 if profile:
434 return profile.decode_select_response(data_hex)
435 else:
436 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100437
Harald Weltec91085e2022-02-10 18:05:45 +0100438
Harald Welteb2edd142021-01-08 23:29:35 +0100439class CardADF(CardDF):
440 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100441
442 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100443 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200444 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200445 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100446 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200447 mf = self.get_mf()
448 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200449 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100450
451 def __str__(self):
452 return "ADF(%s)" % (self.aid)
453
Harald Weltec91085e2022-02-10 18:05:45 +0100454 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100455 if self.name and prefer_name:
456 return self.name
457 else:
458 return self.aid
459
460
461class CardEF(CardFile):
462 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100463
Harald Welteb2edd142021-01-08 23:29:35 +0100464 def __init__(self, *, fid, **kwargs):
465 kwargs['fid'] = fid
466 super().__init__(**kwargs)
467
468 def __str__(self):
469 return "EF(%s)" % (super().__str__())
470
Harald Weltec91085e2022-02-10 18:05:45 +0100471 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200472 """Return a dict of {'identifier': File} that is selectable from the current DF.
473
474 Args:
475 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
476 If not specified, all selectables will be returned.
477 Returns:
478 dict containing all selectable items. Key is identifier (string), value
479 a reference to a CardFile (or derived class) instance.
480 """
Harald Weltec91085e2022-02-10 18:05:45 +0100481 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100482 sels = super().get_selectables(flags)
Harald Weltec91085e2022-02-10 18:05:45 +0100483 sels.update(
484 {x.name: x for x in self.parent.children.values() if x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100485 return sels
486
487
488class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200489 """Transparent EF (Entry File) in the smart card filesystem.
490
491 A Transparent EF is a binary file with no formal structure. This is contrary to
492 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100493
494 @with_default_category('Transparent EF Commands')
495 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200496 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100497
Harald Welteb2edd142021-01-08 23:29:35 +0100498 def __init__(self):
499 super().__init__()
500
501 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100502 read_bin_parser.add_argument(
503 '--offset', type=int, default=0, help='Byte offset for start of read')
504 read_bin_parser.add_argument(
505 '--length', type=int, help='Number of bytes to read')
506
Harald Welteb2edd142021-01-08 23:29:35 +0100507 @cmd2.with_argparser(read_bin_parser)
508 def do_read_binary(self, opts):
509 """Read binary data from a transparent EF"""
510 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
511 self._cmd.poutput(data)
512
Harald Weltebcad86c2021-04-06 20:08:39 +0200513 read_bin_dec_parser = argparse.ArgumentParser()
514 read_bin_dec_parser.add_argument('--oneline', action='store_true',
515 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100516
Harald Weltebcad86c2021-04-06 20:08:39 +0200517 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100518 def do_read_binary_decoded(self, opts):
519 """Read + decode data from a transparent EF"""
520 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200521 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100522
523 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100524 upd_bin_parser.add_argument(
525 '--offset', type=int, default=0, help='Byte offset for start of read')
526 upd_bin_parser.add_argument(
527 'data', help='Data bytes (hex format) to write')
528
Harald Welteb2edd142021-01-08 23:29:35 +0100529 @cmd2.with_argparser(upd_bin_parser)
530 def do_update_binary(self, opts):
531 """Update (Write) data of a transparent EF"""
532 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100533 if data:
534 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100535
536 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100537 upd_bin_dec_parser.add_argument(
538 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200539 upd_bin_dec_parser.add_argument('--json-path', type=str,
540 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100541
Harald Welteb2edd142021-01-08 23:29:35 +0100542 @cmd2.with_argparser(upd_bin_dec_parser)
543 def do_update_binary_decoded(self, opts):
544 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200545 if opts.json_path:
546 (data_json, sw) = self._cmd.rs.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100547 js_path_modify(data_json, opts.json_path,
548 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200549 else:
550 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100551 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100552 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200553 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100554
Harald Welte4145d3c2021-04-08 20:34:13 +0200555 def do_edit_binary_decoded(self, opts):
556 """Edit the JSON representation of the EF contents in an editor."""
557 (orig_json, sw) = self._cmd.rs.read_binary_dec()
558 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
559 filename = '%s/file' % dirname
560 # write existing data as JSON to file
561 with open(filename, 'w') as text_file:
562 json.dump(orig_json, text_file, indent=4)
563 # run a text editor
564 self._cmd._run_editor(filename)
565 with open(filename, 'r') as text_file:
566 edited_json = json.load(text_file)
567 if edited_json == orig_json:
568 self._cmd.poutput("Data not modified, skipping write")
569 else:
570 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
571 if data:
572 self._cmd.poutput_json(data)
573
Harald Weltec91085e2022-02-10 18:05:45 +0100574 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100575 size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200576 """
577 Args:
578 fid : File Identifier (4 hex digits)
579 sfid : Short File Identifier (2 hex digits, optional)
580 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200581 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200582 parent : Parent CardFile object within filesystem hierarchy
583 size : tuple of (minimum_size, recommended_size)
584 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100585 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200586 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200587 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100588 self.size = size
589 self.shell_commands = [self.ShellCommands()]
590
Harald Weltec91085e2022-02-10 18:05:45 +0100591 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200592 """Decode raw (binary) data into abstract representation.
593
594 A derived class would typically provide a _decode_bin() or _decode_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 raw_bin_data : binary encoded data
600 Returns:
601 abstract_data; dict representing the decoded data
602 """
Harald Welteb2edd142021-01-08 23:29:35 +0100603 method = getattr(self, '_decode_bin', None)
604 if callable(method):
605 return method(raw_bin_data)
606 method = getattr(self, '_decode_hex', None)
607 if callable(method):
608 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200609 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200610 return parse_construct(self._construct, raw_bin_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_tlv(raw_bin_data)
614 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100615 return {'raw': raw_bin_data.hex()}
616
Harald Weltec91085e2022-02-10 18:05:45 +0100617 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200618 """Decode raw (hex string) data into abstract representation.
619
620 A derived class would typically provide a _decode_bin() or _decode_hex() method
621 for implementing this specifically for the given file. This function checks which
622 of the method exists, add calls them (with conversion, as needed).
623
624 Args:
625 raw_hex_data : hex-encoded data
626 Returns:
627 abstract_data; dict representing the decoded data
628 """
Harald Welteb2edd142021-01-08 23:29:35 +0100629 method = getattr(self, '_decode_hex', None)
630 if callable(method):
631 return method(raw_hex_data)
632 raw_bin_data = h2b(raw_hex_data)
633 method = getattr(self, '_decode_bin', None)
634 if callable(method):
635 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200636 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200637 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200638 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100639 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100640 t.from_tlv(raw_bin_data)
641 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100642 return {'raw': raw_bin_data.hex()}
643
Harald Weltec91085e2022-02-10 18:05:45 +0100644 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200645 """Encode abstract representation into raw (binary) data.
646
647 A derived class would typically provide an _encode_bin() or _encode_hex() method
648 for implementing this specifically for the given file. This function checks which
649 of the method exists, add calls them (with conversion, as needed).
650
651 Args:
652 abstract_data : dict representing the decoded data
653 Returns:
654 binary encoded data
655 """
Harald Welteb2edd142021-01-08 23:29:35 +0100656 method = getattr(self, '_encode_bin', None)
657 if callable(method):
658 return method(abstract_data)
659 method = getattr(self, '_encode_hex', None)
660 if callable(method):
661 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200662 if self._construct:
663 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200664 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100665 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100666 t.from_dict(abstract_data)
667 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100668 raise NotImplementedError(
669 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100670
Harald Weltec91085e2022-02-10 18:05:45 +0100671 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200672 """Encode abstract representation into raw (hex string) data.
673
674 A derived class would typically provide an _encode_bin() or _encode_hex() method
675 for implementing this specifically for the given file. This function checks which
676 of the method exists, add calls them (with conversion, as needed).
677
678 Args:
679 abstract_data : dict representing the decoded data
680 Returns:
681 hex string encoded data
682 """
Harald Welteb2edd142021-01-08 23:29:35 +0100683 method = getattr(self, '_encode_hex', None)
684 if callable(method):
685 return method(abstract_data)
686 method = getattr(self, '_encode_bin', None)
687 if callable(method):
688 raw_bin_data = method(abstract_data)
689 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200690 if self._construct:
691 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200692 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100693 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100694 t.from_dict(abstract_data)
695 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100696 raise NotImplementedError(
697 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100698
699
700class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200701 """Linear Fixed EF (Entry File) in the smart card filesystem.
702
703 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
704 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100705
706 @with_default_category('Linear Fixed EF Commands')
707 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200708 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100709
Harald Welte9170fbf2022-02-11 21:54:37 +0100710 def __init__(self, **kwargs):
711 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100712
713 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100714 read_rec_parser.add_argument(
715 'record_nr', type=int, help='Number of record to be read')
716 read_rec_parser.add_argument(
717 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
718
Harald Welteb2edd142021-01-08 23:29:35 +0100719 @cmd2.with_argparser(read_rec_parser)
720 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100721 """Read one or multiple records from a record-oriented EF"""
722 for r in range(opts.count):
723 recnr = opts.record_nr + r
724 (data, sw) = self._cmd.rs.read_record(recnr)
725 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100726 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100727 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100728 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100729 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100730
731 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100732 read_rec_dec_parser.add_argument(
733 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200734 read_rec_dec_parser.add_argument('--oneline', action='store_true',
735 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100736
Harald Welteb2edd142021-01-08 23:29:35 +0100737 @cmd2.with_argparser(read_rec_dec_parser)
738 def do_read_record_decoded(self, opts):
739 """Read + decode a record from a record-oriented EF"""
740 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200741 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100742
Harald Welte850b72a2021-04-07 09:33:03 +0200743 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100744
Harald Welte850b72a2021-04-07 09:33:03 +0200745 @cmd2.with_argparser(read_recs_parser)
746 def do_read_records(self, opts):
747 """Read all records from a record-oriented EF"""
748 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
749 for recnr in range(1, 1 + num_of_rec):
750 (data, sw) = self._cmd.rs.read_record(recnr)
751 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100752 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200753 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100754 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200755 self._cmd.poutput("%03d %s" % (recnr, recstr))
756
757 read_recs_dec_parser = argparse.ArgumentParser()
758 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100759 help='No JSON pretty-printing, dump as a single line')
760
Harald Welte850b72a2021-04-07 09:33:03 +0200761 @cmd2.with_argparser(read_recs_dec_parser)
762 def do_read_records_decoded(self, opts):
763 """Read + decode all records from a record-oriented EF"""
764 num_of_rec = self._cmd.rs.selected_file_fcp['file_descriptor']['num_of_rec']
765 # collect all results in list so they are rendered as JSON list when printing
766 data_list = []
767 for recnr in range(1, 1 + num_of_rec):
768 (data, sw) = self._cmd.rs.read_record_dec(recnr)
769 data_list.append(data)
770 self._cmd.poutput_json(data_list, opts.oneline)
771
Harald Welteb2edd142021-01-08 23:29:35 +0100772 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100773 upd_rec_parser.add_argument(
774 'record_nr', type=int, help='Number of record to be read')
775 upd_rec_parser.add_argument(
776 'data', help='Data bytes (hex format) to write')
777
Harald Welteb2edd142021-01-08 23:29:35 +0100778 @cmd2.with_argparser(upd_rec_parser)
779 def do_update_record(self, opts):
780 """Update (write) data to a record-oriented EF"""
781 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100782 if data:
783 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100784
785 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100786 upd_rec_dec_parser.add_argument(
787 'record_nr', type=int, help='Number of record to be read')
788 upd_rec_dec_parser.add_argument(
789 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200790 upd_rec_dec_parser.add_argument('--json-path', type=str,
791 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100792
Harald Welteb2edd142021-01-08 23:29:35 +0100793 @cmd2.with_argparser(upd_rec_dec_parser)
794 def do_update_record_decoded(self, opts):
795 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200796 if opts.json_path:
797 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100798 js_path_modify(data_json, opts.json_path,
799 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200800 else:
801 data_json = json.loads(opts.data)
Harald Weltec91085e2022-02-10 18:05:45 +0100802 (data, sw) = self._cmd.rs.update_record_dec(
803 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100804 if data:
805 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100806
Harald Welte4145d3c2021-04-08 20:34:13 +0200807 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100808 edit_rec_dec_parser.add_argument(
809 'record_nr', type=int, help='Number of record to be edited')
810
Harald Welte4145d3c2021-04-08 20:34:13 +0200811 @cmd2.with_argparser(edit_rec_dec_parser)
812 def do_edit_record_decoded(self, opts):
813 """Edit the JSON representation of one record in an editor."""
814 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200815 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200816 filename = '%s/file' % dirname
817 # write existing data as JSON to file
818 with open(filename, 'w') as text_file:
819 json.dump(orig_json, text_file, indent=4)
820 # run a text editor
821 self._cmd._run_editor(filename)
822 with open(filename, 'r') as text_file:
823 edited_json = json.load(text_file)
824 if edited_json == orig_json:
825 self._cmd.poutput("Data not modified, skipping write")
826 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100827 (data, sw) = self._cmd.rs.update_record_dec(
828 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200829 if data:
830 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200831
Harald Weltec91085e2022-02-10 18:05:45 +0100832 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100833 parent: Optional[CardDF] = None, rec_len={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200834 """
835 Args:
836 fid : File Identifier (4 hex digits)
837 sfid : Short File Identifier (2 hex digits, optional)
838 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200839 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200840 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200841 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200842 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100843 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100844 self.rec_len = rec_len
845 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200846 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200847 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100848
Harald Weltec91085e2022-02-10 18:05:45 +0100849 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200850 """Decode raw (hex string) data into abstract representation.
851
852 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
853 method for implementing this specifically for the given file. This function checks which
854 of the method exists, add calls them (with conversion, as needed).
855
856 Args:
857 raw_hex_data : hex-encoded data
858 Returns:
859 abstract_data; dict representing the decoded data
860 """
Harald Welteb2edd142021-01-08 23:29:35 +0100861 method = getattr(self, '_decode_record_hex', None)
862 if callable(method):
863 return method(raw_hex_data)
864 raw_bin_data = h2b(raw_hex_data)
865 method = getattr(self, '_decode_record_bin', None)
866 if callable(method):
867 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200868 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200869 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200870 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100871 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100872 t.from_tlv(raw_bin_data)
873 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100874 return {'raw': raw_bin_data.hex()}
875
Harald Weltec91085e2022-02-10 18:05:45 +0100876 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200877 """Decode raw (binary) data into abstract representation.
878
879 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
880 method for implementing this specifically for the given file. This function checks which
881 of the method exists, add calls them (with conversion, as needed).
882
883 Args:
884 raw_bin_data : binary encoded data
885 Returns:
886 abstract_data; dict representing the decoded data
887 """
Harald Welteb2edd142021-01-08 23:29:35 +0100888 method = getattr(self, '_decode_record_bin', None)
889 if callable(method):
890 return method(raw_bin_data)
891 raw_hex_data = b2h(raw_bin_data)
892 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 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200896 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200897 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100898 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100899 t.from_tlv(raw_bin_data)
900 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100901 return {'raw': raw_hex_data}
902
Harald Weltec91085e2022-02-10 18:05:45 +0100903 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200904 """Encode abstract representation into raw (hex string) data.
905
906 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
907 method for implementing this specifically for the given file. This function checks which
908 of the method exists, add calls them (with conversion, as needed).
909
910 Args:
911 abstract_data : dict representing the decoded data
912 Returns:
913 hex string encoded data
914 """
Harald Welteb2edd142021-01-08 23:29:35 +0100915 method = getattr(self, '_encode_record_hex', None)
916 if callable(method):
917 return method(abstract_data)
918 method = getattr(self, '_encode_record_bin', None)
919 if callable(method):
920 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200921 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200922 if self._construct:
923 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200924 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100925 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100926 t.from_dict(abstract_data)
927 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100928 raise NotImplementedError(
929 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100930
Harald Weltec91085e2022-02-10 18:05:45 +0100931 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200932 """Encode abstract representation into raw (binary) data.
933
934 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
935 method for implementing this specifically for the given file. This function checks which
936 of the method exists, add calls them (with conversion, as needed).
937
938 Args:
939 abstract_data : dict representing the decoded data
940 Returns:
941 binary encoded data
942 """
Harald Welteb2edd142021-01-08 23:29:35 +0100943 method = getattr(self, '_encode_record_bin', None)
944 if callable(method):
945 return method(abstract_data)
946 method = getattr(self, '_encode_record_hex', None)
947 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200948 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200949 if self._construct:
950 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200951 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100952 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100953 t.from_dict(abstract_data)
954 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100955 raise NotImplementedError(
956 "%s encoder not yet implemented. Patches welcome." % self)
957
Harald Welteb2edd142021-01-08 23:29:35 +0100958
959class CyclicEF(LinFixedEF):
960 """Cyclic EF (Entry File) in the smart card filesystem"""
961 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +0100962
963 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100964 rec_len={1, None}, **kwargs):
965 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100966
Harald Welteb2edd142021-01-08 23:29:35 +0100967
968class TransRecEF(TransparentEF):
969 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +0200970
Harald Welteb2edd142021-01-08 23:29:35 +0100971 These are the real odd-balls and mostly look like mistakes in the specification:
972 Specified as 'transparent' EF, but actually containing several fixed-length records
973 inside.
974 We add a special class for those, so the user only has to provide encoder/decoder functions
975 for a record, while this class takes care of split / merge of records.
976 """
Harald Weltec91085e2022-02-10 18:05:45 +0100977
978 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100979 parent: Optional[CardDF] = None, size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200980 """
981 Args:
982 fid : File Identifier (4 hex digits)
983 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +0200984 name : Brief name of the file, like EF_ICCID
985 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200986 parent : Parent CardFile object within filesystem hierarchy
987 rec_len : Length of the fixed-length records within transparent EF
988 size : tuple of (minimum_size, recommended_size)
989 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100990 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100991 self.rec_len = rec_len
992
Harald Weltec91085e2022-02-10 18:05:45 +0100993 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200994 """Decode raw (hex string) data into abstract representation.
995
996 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
997 method for implementing this specifically for the given file. This function checks which
998 of the method exists, add calls them (with conversion, as needed).
999
1000 Args:
1001 raw_hex_data : hex-encoded data
1002 Returns:
1003 abstract_data; dict representing the decoded data
1004 """
Harald Welteb2edd142021-01-08 23:29:35 +01001005 method = getattr(self, '_decode_record_hex', None)
1006 if callable(method):
1007 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001008 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001009 method = getattr(self, '_decode_record_bin', None)
1010 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001011 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001012 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001013 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001014 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001015 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001016 t.from_tlv(raw_bin_data)
1017 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001018 return {'raw': raw_hex_data}
1019
Harald Weltec91085e2022-02-10 18:05:45 +01001020 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001021 """Decode raw (binary) data into abstract representation.
1022
1023 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1024 method for implementing this specifically for the given file. This function checks which
1025 of the method exists, add calls them (with conversion, as needed).
1026
1027 Args:
1028 raw_bin_data : binary encoded data
1029 Returns:
1030 abstract_data; dict representing the decoded data
1031 """
Harald Welteb2edd142021-01-08 23:29:35 +01001032 method = getattr(self, '_decode_record_bin', None)
1033 if callable(method):
1034 return method(raw_bin_data)
1035 raw_hex_data = b2h(raw_bin_data)
1036 method = getattr(self, '_decode_record_hex', None)
1037 if callable(method):
1038 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001039 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001040 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001041 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001042 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001043 t.from_tlv(raw_bin_data)
1044 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001045 return {'raw': raw_hex_data}
1046
Harald Weltec91085e2022-02-10 18:05:45 +01001047 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001048 """Encode abstract representation into raw (hex string) data.
1049
1050 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1051 method for implementing this specifically for the given file. This function checks which
1052 of the method exists, add calls them (with conversion, as needed).
1053
1054 Args:
1055 abstract_data : dict representing the decoded data
1056 Returns:
1057 hex string encoded data
1058 """
Harald Welteb2edd142021-01-08 23:29:35 +01001059 method = getattr(self, '_encode_record_hex', None)
1060 if callable(method):
1061 return method(abstract_data)
1062 method = getattr(self, '_encode_record_bin', None)
1063 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001064 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001065 if self._construct:
1066 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001067 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001068 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001069 t.from_dict(abstract_data)
1070 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001071 raise NotImplementedError(
1072 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001073
Harald Weltec91085e2022-02-10 18:05:45 +01001074 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001075 """Encode abstract representation into raw (binary) data.
1076
1077 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1078 method for implementing this specifically for the given file. This function checks which
1079 of the method exists, add calls them (with conversion, as needed).
1080
1081 Args:
1082 abstract_data : dict representing the decoded data
1083 Returns:
1084 binary encoded data
1085 """
Harald Welteb2edd142021-01-08 23:29:35 +01001086 method = getattr(self, '_encode_record_bin', None)
1087 if callable(method):
1088 return method(abstract_data)
1089 method = getattr(self, '_encode_record_hex', None)
1090 if callable(method):
1091 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001092 if self._construct:
1093 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001094 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001095 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001096 t.from_dict(abstract_data)
1097 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001098 raise NotImplementedError(
1099 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001100
Harald Weltec91085e2022-02-10 18:05:45 +01001101 def _decode_bin(self, raw_bin_data: bytearray):
1102 chunks = [raw_bin_data[i:i+self.rec_len]
1103 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001104 return [self.decode_record_bin(x) for x in chunks]
1105
Harald Welteee3501f2021-04-02 13:00:18 +02001106 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001107 chunks = [self.encode_record_bin(x) for x in abstract_data]
1108 # FIXME: pad to file size
1109 return b''.join(chunks)
1110
1111
Harald Welte917d98c2021-04-21 11:51:25 +02001112class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001113 """BER-TLV EF (Entry File) in the smart card filesystem.
1114 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001115
Harald Welte27881622021-04-21 11:16:31 +02001116 NOTE: We currently don't really support those, this class is simply a wrapper
1117 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1118 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001119
Harald Welte917d98c2021-04-21 11:51:25 +02001120 @with_default_category('BER-TLV EF Commands')
1121 class ShellCommands(CommandSet):
1122 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001123
Harald Welte917d98c2021-04-21 11:51:25 +02001124 def __init__(self):
1125 super().__init__()
1126
1127 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001128 retrieve_data_parser.add_argument(
1129 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1130
Harald Welte917d98c2021-04-21 11:51:25 +02001131 @cmd2.with_argparser(retrieve_data_parser)
1132 def do_retrieve_data(self, opts):
1133 """Retrieve (Read) data from a BER-TLV EF"""
1134 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1135 self._cmd.poutput(data)
1136
1137 def do_retrieve_tags(self, opts):
1138 """List tags available in a given BER-TLV EF"""
1139 tags = self._cmd.rs.retrieve_tags()
1140 self._cmd.poutput(tags)
1141
1142 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001143 set_data_parser.add_argument(
1144 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1145 set_data_parser.add_argument(
1146 'data', help='Data bytes (hex format) to write')
1147
Harald Welte917d98c2021-04-21 11:51:25 +02001148 @cmd2.with_argparser(set_data_parser)
1149 def do_set_data(self, opts):
1150 """Set (Write) data for a given tag in a BER-TLV EF"""
1151 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1152 if data:
1153 self._cmd.poutput(data)
1154
1155 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001156 del_data_parser.add_argument(
1157 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1158
Harald Welte917d98c2021-04-21 11:51:25 +02001159 @cmd2.with_argparser(del_data_parser)
1160 def do_delete_data(self, opts):
1161 """Delete data for a given tag in a BER-TLV EF"""
1162 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1163 if data:
1164 self._cmd.poutput(data)
1165
Harald Weltec91085e2022-02-10 18:05:45 +01001166 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001167 size={1, None}, **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001168 """
1169 Args:
1170 fid : File Identifier (4 hex digits)
1171 sfid : Short File Identifier (2 hex digits, optional)
1172 name : Brief name of the file, lik EF_ICCID
1173 desc : Description of the file
1174 parent : Parent CardFile object within filesystem hierarchy
1175 size : tuple of (minimum_size, recommended_size)
1176 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001177 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001178 self._construct = None
1179 self.size = size
1180 self.shell_commands = [self.ShellCommands()]
1181
Harald Welteb2edd142021-01-08 23:29:35 +01001182
1183class RuntimeState(object):
1184 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001185
1186 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001187 """
1188 Args:
1189 card : pysim.cards.Card instance
1190 profile : CardProfile instance
1191 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001192 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001193 self.card = card
Harald Weltec91085e2022-02-10 18:05:45 +01001194 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001195 self.profile = profile
Philipp Maier51cad0d2021-11-08 15:45:10 +01001196
1197 # make sure the class and selection control bytes, which are specified
1198 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001199 self.card.set_apdu_parameter(
1200 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001201
Harald Welte5ce35242021-04-02 20:27:05 +02001202 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001203 apps = self._match_applications()
1204 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001205 if a.adf:
1206 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001207 for f in self.profile.files_in_mf:
1208 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001209 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001210
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001211 # make sure that when the runtime state is created, the card is also
1212 # in a defined state.
1213 self.reset()
1214
Philipp Maier1e896f32021-03-10 17:02:53 +01001215 def _match_applications(self):
1216 """match the applications from the profile with applications on the card"""
1217 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001218
1219 # When the profile does not feature any applications, then we are done already
1220 if not apps_profile:
1221 return []
1222
1223 # Read AIDs from card and match them against the applications defined by the
1224 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001225 aids_card = self.card.read_aids()
1226 apps_taken = []
1227 if aids_card:
1228 aids_taken = []
1229 print("AIDs on card:")
1230 for a in aids_card:
1231 for f in apps_profile:
1232 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001233 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001234 aids_taken.append(a)
1235 apps_taken.append(f)
1236 aids_unknown = set(aids_card) - set(aids_taken)
1237 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001238 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001239 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001240 print("warning: EF.DIR seems to be empty!")
1241
1242 # Some card applications may not be registered in EF.DIR, we will actively
1243 # probe for those applications
1244 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001245 try:
1246 data, sw = self.card.select_adf_by_aid(f.aid)
1247 if sw == "9000":
1248 print(" %s: %s" % (f.name, f.aid))
1249 apps_taken.append(f)
1250 except SwMatchError:
1251 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001252 return apps_taken
1253
Harald Weltedaf2b392021-05-03 23:17:29 +02001254 def reset(self, cmd_app=None) -> Hexstr:
1255 """Perform physical card reset and obtain ATR.
1256 Args:
1257 cmd_app : Command Application State (for unregistering old file commands)
1258 """
Philipp Maier946226a2021-10-29 18:31:03 +02001259 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001260 # select MF to reset internal state and to verify card really works
1261 self.select('MF', cmd_app)
1262 return atr
1263
Harald Welteee3501f2021-04-02 13:00:18 +02001264 def get_cwd(self) -> CardDF:
1265 """Obtain the current working directory.
1266
1267 Returns:
1268 CardDF instance
1269 """
Harald Welteb2edd142021-01-08 23:29:35 +01001270 if isinstance(self.selected_file, CardDF):
1271 return self.selected_file
1272 else:
1273 return self.selected_file.parent
1274
Harald Welte5ce35242021-04-02 20:27:05 +02001275 def get_application_df(self) -> Optional[CardADF]:
1276 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001277
1278 Returns:
1279 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001280 # iterate upwards from selected file; check if any is an ADF
1281 node = self.selected_file
1282 while node.parent != node:
1283 if isinstance(node, CardADF):
1284 return node
1285 node = node.parent
1286 return None
1287
Harald Weltec91085e2022-02-10 18:05:45 +01001288 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001289 """Interpret a given status word relative to the currently selected application
1290 or the underlying card profile.
1291
1292 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001293 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001294
1295 Returns:
1296 Tuple of two strings
1297 """
Harald Welte86fbd392021-04-02 22:13:09 +02001298 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001299 adf = self.get_application_df()
1300 if adf:
1301 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001302 # The application either comes with its own interpret_sw
1303 # method or we will use the interpret_sw method from the
1304 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001305 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001306 res = app.interpret_sw(sw)
1307 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001308
Harald Weltec91085e2022-02-10 18:05:45 +01001309 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001310 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001311 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001312 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001313 raise ValueError(
1314 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001315
1316 try:
1317 (data, sw) = self.card._scc.select_file(fid)
1318 except SwMatchError as swm:
1319 k = self.interpret_sw(swm.sw_actual)
1320 if not k:
1321 raise(swm)
1322 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1323
1324 select_resp = self.selected_file.decode_select_response(data)
1325 if (select_resp['file_descriptor']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001326 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1327 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001328 else:
1329 if (select_resp['file_descriptor']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001330 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1331 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001332 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001333 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1334 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001335
1336 self.selected_file.add_files([f])
1337 self.selected_file = f
1338 return select_resp
1339
Harald Weltec91085e2022-02-10 18:05:45 +01001340 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001341 """Select a file (EF, DF, ADF, MF, ...).
1342
1343 Args:
1344 name : Name of file to select
1345 cmd_app : Command Application State (for unregistering old file commands)
1346 """
Harald Welteb2edd142021-01-08 23:29:35 +01001347 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001348 if is_hex(name):
1349 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001350
1351 # unregister commands of old file
1352 if cmd_app and self.selected_file.shell_commands:
1353 for c in self.selected_file.shell_commands:
1354 cmd_app.unregister_command_set(c)
1355
Harald Welteb2edd142021-01-08 23:29:35 +01001356 if name in sels:
1357 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001358 try:
1359 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001360 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001361 else:
1362 (data, sw) = self.card._scc.select_file(f.fid)
1363 self.selected_file = f
1364 except SwMatchError as swm:
1365 k = self.interpret_sw(swm.sw_actual)
1366 if not k:
1367 raise(swm)
1368 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001369 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001370 else:
Philipp Maier63f572d2021-03-09 22:42:47 +01001371 select_resp = self.probe_file(name, cmd_app)
Harald Welte850b72a2021-04-07 09:33:03 +02001372 # store the decoded FCP for later reference
1373 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001374
1375 # register commands of new file
1376 if cmd_app and self.selected_file.shell_commands:
1377 for c in self.selected_file.shell_commands:
1378 cmd_app.register_command_set(c)
1379
1380 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001381
Harald Welte34b05d32021-05-25 22:03:13 +02001382 def status(self):
1383 """Request STATUS (current selected file FCP) from card."""
1384 (data, sw) = self.card._scc.status()
1385 return self.selected_file.decode_select_response(data)
1386
Harald Weltec91085e2022-02-10 18:05:45 +01001387 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001388 """Request ACTIVATE FILE of specified file."""
1389 sels = self.selected_file.get_selectables()
1390 f = sels[name]
1391 data, sw = self.card._scc.activate_file(f.fid)
1392 return data, sw
1393
Harald Weltec91085e2022-02-10 18:05:45 +01001394 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001395 """Read [part of] a transparent EF binary data.
1396
1397 Args:
1398 length : Amount of data to read (None: as much as possible)
1399 offset : Offset into the file from which to read 'length' bytes
1400 Returns:
1401 binary data read from the file
1402 """
Harald Welteb2edd142021-01-08 23:29:35 +01001403 if not isinstance(self.selected_file, TransparentEF):
1404 raise TypeError("Only works with TransparentEF")
1405 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1406
Harald Welte2d4a64b2021-04-03 09:01:24 +02001407 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001408 """Read [part of] a transparent EF binary data and decode it.
1409
1410 Args:
1411 length : Amount of data to read (None: as much as possible)
1412 offset : Offset into the file from which to read 'length' bytes
1413 Returns:
1414 abstract decode data read from the file
1415 """
Harald Welteb2edd142021-01-08 23:29:35 +01001416 (data, sw) = self.read_binary()
1417 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001418 return (dec_data, sw)
1419
Harald Weltec91085e2022-02-10 18:05:45 +01001420 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001421 """Update transparent EF binary data.
1422
1423 Args:
1424 data_hex : hex string of data to be written
1425 offset : Offset into the file from which to write 'data_hex'
1426 """
Harald Welteb2edd142021-01-08 23:29:35 +01001427 if not isinstance(self.selected_file, TransparentEF):
1428 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001429 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001430
Harald Weltec91085e2022-02-10 18:05:45 +01001431 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001432 """Update transparent EF from abstract data. Encodes the data to binary and
1433 then updates the EF with it.
1434
1435 Args:
1436 data : abstract data which is to be encoded and written
1437 """
Harald Welteb2edd142021-01-08 23:29:35 +01001438 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001439 return self.update_binary(data_hex)
1440
Harald Weltec91085e2022-02-10 18:05:45 +01001441 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001442 """Read a record as binary data.
1443
1444 Args:
1445 rec_nr : Record number to read
1446 Returns:
1447 hex string of binary data contained in record
1448 """
Harald Welteb2edd142021-01-08 23:29:35 +01001449 if not isinstance(self.selected_file, LinFixedEF):
1450 raise TypeError("Only works with Linear Fixed EF")
1451 # returns a string of hex nibbles
1452 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1453
Harald Weltec91085e2022-02-10 18:05:45 +01001454 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001455 """Read a record and decode it to abstract data.
1456
1457 Args:
1458 rec_nr : Record number to read
1459 Returns:
1460 abstract data contained in record
1461 """
Harald Welteb2edd142021-01-08 23:29:35 +01001462 (data, sw) = self.read_record(rec_nr)
1463 return (self.selected_file.decode_record_hex(data), sw)
1464
Harald Weltec91085e2022-02-10 18:05:45 +01001465 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001466 """Update a record with given binary data
1467
1468 Args:
1469 rec_nr : Record number to read
1470 data_hex : Hex string binary data to be written
1471 """
Harald Welteb2edd142021-01-08 23:29:35 +01001472 if not isinstance(self.selected_file, LinFixedEF):
1473 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001474 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 +01001475
Harald Weltec91085e2022-02-10 18:05:45 +01001476 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001477 """Update a record with given abstract data. Will encode abstract to binary data
1478 and then write it to the given record on the card.
1479
1480 Args:
1481 rec_nr : Record number to read
1482 data_hex : Abstract data to be written
1483 """
Harald Welte1e456572021-04-02 17:16:30 +02001484 data_hex = self.selected_file.encode_record_hex(data)
1485 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001486
Harald Weltec91085e2022-02-10 18:05:45 +01001487 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001488 """Read a DO/TLV as binary data.
1489
1490 Args:
1491 tag : Tag of TLV/DO to read
1492 Returns:
1493 hex string of full BER-TLV DO including Tag and Length
1494 """
1495 if not isinstance(self.selected_file, BerTlvEF):
1496 raise TypeError("Only works with BER-TLV EF")
1497 # returns a string of hex nibbles
1498 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1499
1500 def retrieve_tags(self):
1501 """Retrieve tags available on BER-TLV EF.
1502
1503 Returns:
1504 list of integer tags contained in EF
1505 """
1506 if not isinstance(self.selected_file, BerTlvEF):
1507 raise TypeError("Only works with BER-TLV EF")
1508 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001509 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001510 return list(value)
1511
Harald Weltec91085e2022-02-10 18:05:45 +01001512 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001513 """Update a TLV/DO with given binary data
1514
1515 Args:
1516 tag : Tag of TLV/DO to be written
1517 data_hex : Hex string binary data to be written (value portion)
1518 """
1519 if not isinstance(self.selected_file, BerTlvEF):
1520 raise TypeError("Only works with BER-TLV EF")
1521 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1522
Philipp Maier5d698e52021-09-16 13:18:01 +02001523 def unregister_cmds(self, cmd_app=None):
1524 """Unregister all file specific commands."""
1525 if cmd_app and self.selected_file.shell_commands:
1526 for c in self.selected_file.shell_commands:
1527 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001528
Harald Welteb2edd142021-01-08 23:29:35 +01001529
Harald Welteb2edd142021-01-08 23:29:35 +01001530class FileData(object):
1531 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001532
Harald Welteb2edd142021-01-08 23:29:35 +01001533 def __init__(self, fdesc):
1534 self.desc = fdesc
1535 self.fcp = None
1536
1537
Harald Weltec91085e2022-02-10 18:05:45 +01001538def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001539 """Interpret a given status word.
1540
1541 Args:
1542 sw_data : Hierarchical dict of status word matches
1543 sw : status word to match (string of 4 hex digits)
1544 Returns:
1545 tuple of two strings (class_string, description)
1546 """
Harald Welteb2edd142021-01-08 23:29:35 +01001547 for class_str, swdict in sw_data.items():
1548 # first try direct match
1549 if sw in swdict:
1550 return (class_str, swdict[sw])
1551 # next try wildcard matches
1552 for pattern, descr in swdict.items():
1553 if sw_match(sw, pattern):
1554 return (class_str, descr)
1555 return None
1556
Harald Weltec91085e2022-02-10 18:05:45 +01001557
Harald Welteb2edd142021-01-08 23:29:35 +01001558class CardApplication(object):
1559 """A card application is represented by an ADF (with contained hierarchy) and optionally
1560 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001561
1562 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001563 """
1564 Args:
1565 adf : ADF name
1566 sw : Dict of status word conversions
1567 """
Harald Welteb2edd142021-01-08 23:29:35 +01001568 self.name = name
1569 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001570 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001571 # back-reference from ADF to Applicaiton
1572 if self.adf:
1573 self.aid = aid or self.adf.aid
1574 self.adf.application = self
1575 else:
1576 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001577
1578 def __str__(self):
1579 return "APP(%s)" % (self.name)
1580
1581 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001582 """Interpret a given status word within the application.
1583
1584 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001585 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001586
1587 Returns:
1588 Tuple of two strings
1589 """
Harald Welteb2edd142021-01-08 23:29:35 +01001590 return interpret_sw(self.sw, sw)
1591
Harald Weltef44256c2021-10-14 15:53:39 +02001592
1593class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001594 """A specific card model, typically having some additional vendor-specific files. All
1595 you need to do is to define a sub-class with a list of ATRs or an overridden match
1596 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001597 _atrs = []
1598
1599 @classmethod
1600 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001601 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001602 """Add model specific files to given RuntimeState."""
1603
1604 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001605 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001606 """Test if given card matches this model."""
1607 card_atr = scc.get_atr()
1608 for atr in cls._atrs:
1609 atr_bin = toBytes(atr)
1610 if atr_bin == card_atr:
1611 print("Detected CardModel:", cls.__name__)
1612 return True
1613 return False
1614
1615 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001616 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001617 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1618 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1619 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001620 for m in CardModel.__subclasses__():
1621 if m.match(scc):
1622 m.add_files(rs)