blob: 37c668d759bc9dc097bfd2eaf0ba0cc95737b8b3 [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
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +070052class CardFile:
Harald Welteb2edd142021-01-08 23:29:35 +010053 """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 Welteaceb2a52022-02-12 21:41:59 +0100118 def fully_qualified_path_fobj(self) -> List['CardFile']:
119 """Return fully qualified path to file as list of CardFile instance references."""
120 if self.parent and self.parent != self:
121 ret = self.parent.fully_qualified_path_fobj()
122 else:
123 ret = []
124 if self:
125 ret.append(self)
126 return ret
127
128 def build_select_path_to(self, target: 'CardFile') -> Optional[List['CardFile']]:
129 """Build the relative sequence of files we need to traverse to get from us to 'target'."""
130 cur_fqpath = self.fully_qualified_path_fobj()
131 target_fqpath = target.fully_qualified_path_fobj()
132 inter_path = []
133 cur_fqpath.pop() # drop last element (currently selected file, doesn't need re-selection
134 cur_fqpath.reverse()
135 for ce in cur_fqpath:
136 inter_path.append(ce)
137 for i in range(0, len(target_fqpath)-1):
138 te = target_fqpath[i]
139 if te == ce:
140 for te2 in target_fqpath[i+1:]:
141 inter_path.append(te2)
142 # we found our common ancestor
143 return inter_path
144 return None
145
Harald Welteee3501f2021-04-02 13:00:18 +0200146 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100147 """Return the MF (root) of the file system."""
148 if self.parent == None:
149 return None
150 # iterate towards the top. MF has parent == self
151 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200152 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100153 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200154 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100155
Harald Weltec91085e2022-02-10 18:05:45 +0100156 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200157 """Return a dict of {'identifier': self} tuples.
158
159 Args:
160 alias : Add an alias with given name to 'self'
161 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
162 If not specified, all selectables will be returned.
163 Returns:
164 dict containing reference to 'self' for all identifiers.
165 """
Harald Welteb2edd142021-01-08 23:29:35 +0100166 sels = {}
167 if alias:
168 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100169 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100170 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100171 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100172 sels.update({self.name: self})
173 return sels
174
Harald Weltec91085e2022-02-10 18:05:45 +0100175 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200176 """Return a dict of {'identifier': File} that is selectable from the current file.
177
178 Args:
179 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
180 If not specified, all selectables will be returned.
181 Returns:
182 dict containing all selectable items. Key is identifier (string), value
183 a reference to a CardFile (or derived class) instance.
184 """
Philipp Maier786f7812021-02-25 16:48:10 +0100185 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100186 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100187 if flags == [] or 'SELF' in flags:
188 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100189 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100190 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200191 if self.parent:
192 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100193 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100194 if flags == [] or 'MF' in flags:
195 mf = self.get_mf()
196 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100197 sels.update(mf._get_self_selectables(flags=flags))
198 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100199 return sels
200
Harald Weltec91085e2022-02-10 18:05:45 +0100201 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200202 """Return a dict of {'identifier': File} that is selectable from the current file.
203
204 Args:
205 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
206 If not specified, all selectables will be returned.
207 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200208 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200209 """
Philipp Maier786f7812021-02-25 16:48:10 +0100210 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100211 sel_keys = list(sels.keys())
212 sel_keys.sort()
213 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100214
Harald Weltec91085e2022-02-10 18:05:45 +0100215 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100216 """Decode the response to a SELECT command.
217
218 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100219 data_hex: Hex string of the select response
220 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100221
Harald Weltec91085e2022-02-10 18:05:45 +0100222 # When the current file does not implement a custom select response decoder,
223 # we just ask the parent file to decode the select response. If this method
224 # is not overloaded by the current file we will again ask the parent file.
225 # This way we recursively travel up the file system tree until we hit a file
226 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200227 if self.parent:
228 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100229
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100230 def get_profile(self):
231 """Get the profile associated with this file. If this file does not have any
232 profile assigned, try to find a file above (usually the MF) in the filesystem
233 hirarchy that has a profile assigned
234 """
235
236 # If we have a profile set, return it
237 if self.profile:
238 return self.profile
239
240 # Walk up recursively until we hit a parent that has a profile set
241 if self.parent:
242 return self.parent.get_profile()
243 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100244
Harald Welte9170fbf2022-02-11 21:54:37 +0100245 def should_exist_for_services(self, services: List[int]):
246 """Assuming the provided list of activated services, should this file exist and be activated?."""
247 if self.service is None:
248 return None
249 elif isinstance(self.service, int):
250 # a single service determines the result
251 return self.service in services
252 elif isinstance(self.service, list):
253 # any of the services active -> true
254 for s in self.service:
255 if s in services:
256 return True
257 return False
258 elif isinstance(self.service, tuple):
259 # all of the services active -> true
260 for s in self.service:
261 if not s in services:
262 return False
263 return True
264 else:
265 raise ValueError("self.service must be either int or list or tuple")
266
Harald Weltec91085e2022-02-10 18:05:45 +0100267
Harald Welteb2edd142021-01-08 23:29:35 +0100268class CardDF(CardFile):
269 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100270
271 @with_default_category('DF/ADF Commands')
272 class ShellCommands(CommandSet):
273 def __init__(self):
274 super().__init__()
275
Harald Welteb2edd142021-01-08 23:29:35 +0100276 def __init__(self, **kwargs):
277 if not isinstance(self, CardADF):
278 if not 'fid' in kwargs:
279 raise TypeError('fid is mandatory for all DF')
280 super().__init__(**kwargs)
281 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100282 self.shell_commands = [self.ShellCommands()]
Harald Welte9170fbf2022-02-11 21:54:37 +0100283 # dict of CardFile affected by service(int), indexed by service
284 self.files_by_service = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100285
286 def __str__(self):
287 return "DF(%s)" % (super().__str__())
288
Harald Welte9170fbf2022-02-11 21:54:37 +0100289 def _add_file_services(self, child):
290 """Add a child (DF/EF) to the files_by_services of the parent."""
291 if not child.service:
292 return
293 if isinstance(child.service, int):
294 self.files_by_service.setdefault(child.service, []).append(child)
295 elif isinstance(child.service, list):
296 for service in child.service:
297 self.files_by_service.setdefault(service, []).append(child)
298 elif isinstance(child.service, tuple):
299 for service in child.service:
300 self.files_by_service.setdefault(service, []).append(child)
301 else:
302 raise ValueError
303
Harald Weltec91085e2022-02-10 18:05:45 +0100304 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200305 """Add a child (DF/EF) to this DF.
306 Args:
307 child: The new DF/EF to be added
308 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
309 """
Harald Welteb2edd142021-01-08 23:29:35 +0100310 if not isinstance(child, CardFile):
311 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100312 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100313 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100314 if child.name in CardFile.RESERVED_NAMES:
315 raise ValueError("File name %s is a reserved name" % (child.name))
316 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100317 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100318 if child.fid in self.children:
319 if ignore_existing:
320 return
Harald Weltec91085e2022-02-10 18:05:45 +0100321 raise ValueError(
322 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100323 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100324 raise ValueError(
325 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100326 if self.lookup_file_by_name(child.name):
327 if ignore_existing:
328 return
Harald Weltec91085e2022-02-10 18:05:45 +0100329 raise ValueError(
330 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100331 self.children[child.fid] = child
332 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100333 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100334 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100335 if isinstance(child, CardDF):
336 for c in child.children.values():
337 self._add_file_services(c)
338 if isinstance(c, CardDF):
339 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100340
Harald Weltec91085e2022-02-10 18:05:45 +0100341 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200342 """Add a list of child (DF/EF) to this DF
343
344 Args:
345 children: List of new DF/EFs to be added
346 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
347 """
Harald Welteb2edd142021-01-08 23:29:35 +0100348 for child in children:
349 self.add_file(child, ignore_existing)
350
Harald Weltec91085e2022-02-10 18:05:45 +0100351 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200352 """Return a dict of {'identifier': File} that is selectable from the current DF.
353
354 Args:
355 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
356 If not specified, all selectables will be returned.
357 Returns:
358 dict containing all selectable items. Key is identifier (string), value
359 a reference to a CardFile (or derived class) instance.
360 """
Harald Welteb2edd142021-01-08 23:29:35 +0100361 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100362 sels = super().get_selectables(flags)
363 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100364 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100365 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100366 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100367 return sels
368
Harald Weltec91085e2022-02-10 18:05:45 +0100369 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200370 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100371 if name == None:
372 return None
373 for i in self.children.values():
374 if i.name and i.name == name:
375 return i
376 return None
377
Harald Weltec91085e2022-02-10 18:05:45 +0100378 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200379 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100380 if sfid == None:
381 return None
382 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200383 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100384 return i
385 return None
386
Harald Weltec91085e2022-02-10 18:05:45 +0100387 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200388 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100389 if fid in self.children:
390 return self.children[fid]
391 return None
392
393
394class CardMF(CardDF):
395 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100396
Harald Welteb2edd142021-01-08 23:29:35 +0100397 def __init__(self, **kwargs):
398 # can be overridden; use setdefault
399 kwargs.setdefault('fid', '3f00')
400 kwargs.setdefault('name', 'MF')
401 kwargs.setdefault('desc', 'Master File (directory root)')
402 # cannot be overridden; use assignment
403 kwargs['parent'] = self
404 super().__init__(**kwargs)
405 self.applications = dict()
406
407 def __str__(self):
408 return "MF(%s)" % (self.fid)
409
Harald Weltec91085e2022-02-10 18:05:45 +0100410 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200411 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100412 if not isinstance(app, CardADF):
413 raise TypeError("Expected an ADF instance")
414 if app.aid in self.applications:
415 raise ValueError("AID %s already exists" % (app.aid))
416 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100417 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100418
419 def get_app_names(self):
420 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100421 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100422
Harald Weltec91085e2022-02-10 18:05:45 +0100423 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200424 """Return a dict of {'identifier': File} that is selectable from the current DF.
425
426 Args:
427 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
428 If not specified, all selectables will be returned.
429 Returns:
430 dict containing all selectable items. Key is identifier (string), value
431 a reference to a CardFile (or derived class) instance.
432 """
Philipp Maier786f7812021-02-25 16:48:10 +0100433 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100434 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100435 return sels
436
Harald Weltec91085e2022-02-10 18:05:45 +0100437 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100438 """Get applications by AID + name"""
439 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100440 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100441 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100442 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100443 sels.update(
444 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100445 return sels
446
Harald Weltec9752512022-02-11 16:31:15 +0100447 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200448 """Decode the response to a SELECT command.
449
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100450 This is the fall-back method which automatically defers to the standard decoding
451 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100452 performed. Specific derived classes (usually ADF) can overload this method to
453 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200454 """
Harald Welteb2edd142021-01-08 23:29:35 +0100455
Harald Weltec9752512022-02-11 16:31:15 +0100456 if not data_hex:
457 return data_hex
458
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100459 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100460
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100461 if profile:
462 return profile.decode_select_response(data_hex)
463 else:
464 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100465
Harald Weltec91085e2022-02-10 18:05:45 +0100466
Harald Welteb2edd142021-01-08 23:29:35 +0100467class CardADF(CardDF):
468 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100469
470 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100471 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200472 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200473 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100474 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200475 mf = self.get_mf()
476 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200477 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100478
479 def __str__(self):
480 return "ADF(%s)" % (self.aid)
481
Harald Weltec91085e2022-02-10 18:05:45 +0100482 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100483 if self.name and prefer_name:
484 return self.name
485 else:
486 return self.aid
487
488
489class CardEF(CardFile):
490 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100491
Harald Welteb2edd142021-01-08 23:29:35 +0100492 def __init__(self, *, fid, **kwargs):
493 kwargs['fid'] = fid
494 super().__init__(**kwargs)
495
496 def __str__(self):
497 return "EF(%s)" % (super().__str__())
498
Harald Weltec91085e2022-02-10 18:05:45 +0100499 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200500 """Return a dict of {'identifier': File} that is selectable from the current DF.
501
502 Args:
503 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
504 If not specified, all selectables will be returned.
505 Returns:
506 dict containing all selectable items. Key is identifier (string), value
507 a reference to a CardFile (or derived class) instance.
508 """
Harald Weltec91085e2022-02-10 18:05:45 +0100509 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100510 sels = super().get_selectables(flags)
Harald Weltec91085e2022-02-10 18:05:45 +0100511 sels.update(
512 {x.name: x for x in self.parent.children.values() if x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100513 return sels
514
515
516class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200517 """Transparent EF (Entry File) in the smart card filesystem.
518
519 A Transparent EF is a binary file with no formal structure. This is contrary to
520 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100521
522 @with_default_category('Transparent EF Commands')
523 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200524 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100525
Harald Welteb2edd142021-01-08 23:29:35 +0100526 def __init__(self):
527 super().__init__()
528
Harald Welteaefd0642022-02-25 15:26:37 +0100529 dec_hex_parser = argparse.ArgumentParser()
530 dec_hex_parser.add_argument('--oneline', action='store_true',
531 help='No JSON pretty-printing, dump as a single line')
532 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
533
534 @cmd2.with_argparser(dec_hex_parser)
535 def do_decode_hex(self, opts):
536 """Decode command-line provided hex-string as if it was read from the file."""
537 data = self._cmd.rs.selected_file.decode_hex(opts.HEXSTR)
538 self._cmd.poutput_json(data, opts.oneline)
539
Harald Welteb2edd142021-01-08 23:29:35 +0100540 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100541 read_bin_parser.add_argument(
542 '--offset', type=int, default=0, help='Byte offset for start of read')
543 read_bin_parser.add_argument(
544 '--length', type=int, help='Number of bytes to read')
545
Harald Welteb2edd142021-01-08 23:29:35 +0100546 @cmd2.with_argparser(read_bin_parser)
547 def do_read_binary(self, opts):
548 """Read binary data from a transparent EF"""
549 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
550 self._cmd.poutput(data)
551
Harald Weltebcad86c2021-04-06 20:08:39 +0200552 read_bin_dec_parser = argparse.ArgumentParser()
553 read_bin_dec_parser.add_argument('--oneline', action='store_true',
554 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100555
Harald Weltebcad86c2021-04-06 20:08:39 +0200556 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100557 def do_read_binary_decoded(self, opts):
558 """Read + decode data from a transparent EF"""
559 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200560 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100561
562 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100563 upd_bin_parser.add_argument(
564 '--offset', type=int, default=0, help='Byte offset for start of read')
565 upd_bin_parser.add_argument(
566 'data', help='Data bytes (hex format) to write')
567
Harald Welteb2edd142021-01-08 23:29:35 +0100568 @cmd2.with_argparser(upd_bin_parser)
569 def do_update_binary(self, opts):
570 """Update (Write) data of a transparent EF"""
571 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100572 if data:
573 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100574
575 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100576 upd_bin_dec_parser.add_argument(
577 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200578 upd_bin_dec_parser.add_argument('--json-path', type=str,
579 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100580
Harald Welteb2edd142021-01-08 23:29:35 +0100581 @cmd2.with_argparser(upd_bin_dec_parser)
582 def do_update_binary_decoded(self, opts):
583 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200584 if opts.json_path:
585 (data_json, sw) = self._cmd.rs.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100586 js_path_modify(data_json, opts.json_path,
587 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200588 else:
589 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100590 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100591 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200592 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100593
Harald Welte4145d3c2021-04-08 20:34:13 +0200594 def do_edit_binary_decoded(self, opts):
595 """Edit the JSON representation of the EF contents in an editor."""
596 (orig_json, sw) = self._cmd.rs.read_binary_dec()
597 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
598 filename = '%s/file' % dirname
599 # write existing data as JSON to file
600 with open(filename, 'w') as text_file:
601 json.dump(orig_json, text_file, indent=4)
602 # run a text editor
603 self._cmd._run_editor(filename)
604 with open(filename, 'r') as text_file:
605 edited_json = json.load(text_file)
606 if edited_json == orig_json:
607 self._cmd.poutput("Data not modified, skipping write")
608 else:
609 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
610 if data:
611 self._cmd.poutput_json(data)
612
Harald Weltec91085e2022-02-10 18:05:45 +0100613 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100614 size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200615 """
616 Args:
617 fid : File Identifier (4 hex digits)
618 sfid : Short File Identifier (2 hex digits, optional)
619 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200620 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200621 parent : Parent CardFile object within filesystem hierarchy
622 size : tuple of (minimum_size, recommended_size)
623 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100624 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200625 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200626 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100627 self.size = size
628 self.shell_commands = [self.ShellCommands()]
629
Harald Weltec91085e2022-02-10 18:05:45 +0100630 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200631 """Decode raw (binary) data into abstract representation.
632
633 A derived class would typically provide a _decode_bin() or _decode_hex() method
634 for implementing this specifically for the given file. This function checks which
635 of the method exists, add calls them (with conversion, as needed).
636
637 Args:
638 raw_bin_data : binary encoded data
639 Returns:
640 abstract_data; dict representing the decoded data
641 """
Harald Welteb2edd142021-01-08 23:29:35 +0100642 method = getattr(self, '_decode_bin', None)
643 if callable(method):
644 return method(raw_bin_data)
645 method = getattr(self, '_decode_hex', None)
646 if callable(method):
647 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200648 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200649 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200650 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100651 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100652 t.from_tlv(raw_bin_data)
653 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100654 return {'raw': raw_bin_data.hex()}
655
Harald Weltec91085e2022-02-10 18:05:45 +0100656 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200657 """Decode raw (hex string) data into abstract representation.
658
659 A derived class would typically provide a _decode_bin() or _decode_hex() method
660 for implementing this specifically for the given file. This function checks which
661 of the method exists, add calls them (with conversion, as needed).
662
663 Args:
664 raw_hex_data : hex-encoded data
665 Returns:
666 abstract_data; dict representing the decoded data
667 """
Harald Welteb2edd142021-01-08 23:29:35 +0100668 method = getattr(self, '_decode_hex', None)
669 if callable(method):
670 return method(raw_hex_data)
671 raw_bin_data = h2b(raw_hex_data)
672 method = getattr(self, '_decode_bin', None)
673 if callable(method):
674 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200675 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200676 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200677 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100678 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100679 t.from_tlv(raw_bin_data)
680 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100681 return {'raw': raw_bin_data.hex()}
682
Harald Weltec91085e2022-02-10 18:05:45 +0100683 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200684 """Encode abstract representation into raw (binary) data.
685
686 A derived class would typically provide an _encode_bin() or _encode_hex() method
687 for implementing this specifically for the given file. This function checks which
688 of the method exists, add calls them (with conversion, as needed).
689
690 Args:
691 abstract_data : dict representing the decoded data
692 Returns:
693 binary encoded data
694 """
Harald Welteb2edd142021-01-08 23:29:35 +0100695 method = getattr(self, '_encode_bin', None)
696 if callable(method):
697 return method(abstract_data)
698 method = getattr(self, '_encode_hex', None)
699 if callable(method):
700 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200701 if self._construct:
702 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200703 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100704 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100705 t.from_dict(abstract_data)
706 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100707 raise NotImplementedError(
708 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100709
Harald Weltec91085e2022-02-10 18:05:45 +0100710 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200711 """Encode abstract representation into raw (hex string) data.
712
713 A derived class would typically provide an _encode_bin() or _encode_hex() method
714 for implementing this specifically for the given file. This function checks which
715 of the method exists, add calls them (with conversion, as needed).
716
717 Args:
718 abstract_data : dict representing the decoded data
719 Returns:
720 hex string encoded data
721 """
Harald Welteb2edd142021-01-08 23:29:35 +0100722 method = getattr(self, '_encode_hex', None)
723 if callable(method):
724 return method(abstract_data)
725 method = getattr(self, '_encode_bin', None)
726 if callable(method):
727 raw_bin_data = method(abstract_data)
728 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200729 if self._construct:
730 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200731 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100732 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100733 t.from_dict(abstract_data)
734 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100735 raise NotImplementedError(
736 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100737
738
739class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200740 """Linear Fixed EF (Entry File) in the smart card filesystem.
741
742 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
743 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100744
745 @with_default_category('Linear Fixed EF Commands')
746 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200747 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100748
Harald Welte9170fbf2022-02-11 21:54:37 +0100749 def __init__(self, **kwargs):
750 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100751
Harald Welteaefd0642022-02-25 15:26:37 +0100752 dec_hex_parser = argparse.ArgumentParser()
753 dec_hex_parser.add_argument('--oneline', action='store_true',
754 help='No JSON pretty-printing, dump as a single line')
755 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
756
757 @cmd2.with_argparser(dec_hex_parser)
758 def do_decode_hex(self, opts):
759 """Decode command-line provided hex-string as if it was read from the file."""
760 data = self._cmd.rs.selected_file.decode_record_hex(opts.HEXSTR)
761 self._cmd.poutput_json(data, opts.oneline)
762
Harald Welteb2edd142021-01-08 23:29:35 +0100763 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100764 read_rec_parser.add_argument(
765 'record_nr', type=int, help='Number of record to be read')
766 read_rec_parser.add_argument(
767 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
768
Harald Welteb2edd142021-01-08 23:29:35 +0100769 @cmd2.with_argparser(read_rec_parser)
770 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100771 """Read one or multiple records from a record-oriented EF"""
772 for r in range(opts.count):
773 recnr = opts.record_nr + r
774 (data, sw) = self._cmd.rs.read_record(recnr)
775 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100776 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100777 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100778 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100779 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100780
781 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100782 read_rec_dec_parser.add_argument(
783 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200784 read_rec_dec_parser.add_argument('--oneline', action='store_true',
785 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100786
Harald Welteb2edd142021-01-08 23:29:35 +0100787 @cmd2.with_argparser(read_rec_dec_parser)
788 def do_read_record_decoded(self, opts):
789 """Read + decode a record from a record-oriented EF"""
790 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200791 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100792
Harald Welte850b72a2021-04-07 09:33:03 +0200793 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100794
Harald Welte850b72a2021-04-07 09:33:03 +0200795 @cmd2.with_argparser(read_recs_parser)
796 def do_read_records(self, opts):
797 """Read all records from a record-oriented EF"""
Harald Welte747a9782022-02-13 17:52:28 +0100798 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200799 for recnr in range(1, 1 + num_of_rec):
800 (data, sw) = self._cmd.rs.read_record(recnr)
801 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100802 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200803 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100804 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200805 self._cmd.poutput("%03d %s" % (recnr, recstr))
806
807 read_recs_dec_parser = argparse.ArgumentParser()
808 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100809 help='No JSON pretty-printing, dump as a single line')
810
Harald Welte850b72a2021-04-07 09:33:03 +0200811 @cmd2.with_argparser(read_recs_dec_parser)
812 def do_read_records_decoded(self, opts):
813 """Read + decode all records from a record-oriented EF"""
Harald Welte747a9782022-02-13 17:52:28 +0100814 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200815 # collect all results in list so they are rendered as JSON list when printing
816 data_list = []
817 for recnr in range(1, 1 + num_of_rec):
818 (data, sw) = self._cmd.rs.read_record_dec(recnr)
819 data_list.append(data)
820 self._cmd.poutput_json(data_list, opts.oneline)
821
Harald Welteb2edd142021-01-08 23:29:35 +0100822 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100823 upd_rec_parser.add_argument(
824 'record_nr', type=int, help='Number of record to be read')
825 upd_rec_parser.add_argument(
826 'data', help='Data bytes (hex format) to write')
827
Harald Welteb2edd142021-01-08 23:29:35 +0100828 @cmd2.with_argparser(upd_rec_parser)
829 def do_update_record(self, opts):
830 """Update (write) data to a record-oriented EF"""
831 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100832 if data:
833 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100834
835 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100836 upd_rec_dec_parser.add_argument(
837 'record_nr', type=int, help='Number of record to be read')
838 upd_rec_dec_parser.add_argument(
839 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200840 upd_rec_dec_parser.add_argument('--json-path', type=str,
841 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100842
Harald Welteb2edd142021-01-08 23:29:35 +0100843 @cmd2.with_argparser(upd_rec_dec_parser)
844 def do_update_record_decoded(self, opts):
845 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200846 if opts.json_path:
847 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100848 js_path_modify(data_json, opts.json_path,
849 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200850 else:
851 data_json = json.loads(opts.data)
Harald Weltec91085e2022-02-10 18:05:45 +0100852 (data, sw) = self._cmd.rs.update_record_dec(
853 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100854 if data:
855 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100856
Harald Welte4145d3c2021-04-08 20:34:13 +0200857 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100858 edit_rec_dec_parser.add_argument(
859 'record_nr', type=int, help='Number of record to be edited')
860
Harald Welte4145d3c2021-04-08 20:34:13 +0200861 @cmd2.with_argparser(edit_rec_dec_parser)
862 def do_edit_record_decoded(self, opts):
863 """Edit the JSON representation of one record in an editor."""
864 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200865 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200866 filename = '%s/file' % dirname
867 # write existing data as JSON to file
868 with open(filename, 'w') as text_file:
869 json.dump(orig_json, text_file, indent=4)
870 # run a text editor
871 self._cmd._run_editor(filename)
872 with open(filename, 'r') as text_file:
873 edited_json = json.load(text_file)
874 if edited_json == orig_json:
875 self._cmd.poutput("Data not modified, skipping write")
876 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100877 (data, sw) = self._cmd.rs.update_record_dec(
878 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200879 if data:
880 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200881
Harald Weltec91085e2022-02-10 18:05:45 +0100882 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100883 parent: Optional[CardDF] = None, rec_len={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200884 """
885 Args:
886 fid : File Identifier (4 hex digits)
887 sfid : Short File Identifier (2 hex digits, optional)
888 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200889 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200890 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200891 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200892 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100893 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100894 self.rec_len = rec_len
895 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200896 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200897 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100898
Harald Weltec91085e2022-02-10 18:05:45 +0100899 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200900 """Decode raw (hex string) data into abstract representation.
901
902 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
903 method for implementing this specifically for the given file. This function checks which
904 of the method exists, add calls them (with conversion, as needed).
905
906 Args:
907 raw_hex_data : hex-encoded data
908 Returns:
909 abstract_data; dict representing the decoded data
910 """
Harald Welteb2edd142021-01-08 23:29:35 +0100911 method = getattr(self, '_decode_record_hex', None)
912 if callable(method):
913 return method(raw_hex_data)
914 raw_bin_data = h2b(raw_hex_data)
915 method = getattr(self, '_decode_record_bin', None)
916 if callable(method):
917 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200918 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200919 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200920 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100921 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100922 t.from_tlv(raw_bin_data)
923 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100924 return {'raw': raw_bin_data.hex()}
925
Harald Weltec91085e2022-02-10 18:05:45 +0100926 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200927 """Decode raw (binary) data into abstract representation.
928
929 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
930 method for implementing this specifically for the given file. This function checks which
931 of the method exists, add calls them (with conversion, as needed).
932
933 Args:
934 raw_bin_data : binary encoded data
935 Returns:
936 abstract_data; dict representing the decoded data
937 """
Harald Welteb2edd142021-01-08 23:29:35 +0100938 method = getattr(self, '_decode_record_bin', None)
939 if callable(method):
940 return method(raw_bin_data)
941 raw_hex_data = b2h(raw_bin_data)
942 method = getattr(self, '_decode_record_hex', None)
943 if callable(method):
944 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200945 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200946 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200947 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100948 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100949 t.from_tlv(raw_bin_data)
950 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100951 return {'raw': raw_hex_data}
952
Harald Weltec91085e2022-02-10 18:05:45 +0100953 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200954 """Encode abstract representation into raw (hex string) data.
955
956 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
957 method for implementing this specifically for the given file. This function checks which
958 of the method exists, add calls them (with conversion, as needed).
959
960 Args:
961 abstract_data : dict representing the decoded data
962 Returns:
963 hex string encoded data
964 """
Harald Welteb2edd142021-01-08 23:29:35 +0100965 method = getattr(self, '_encode_record_hex', None)
966 if callable(method):
967 return method(abstract_data)
968 method = getattr(self, '_encode_record_bin', None)
969 if callable(method):
970 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200971 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200972 if self._construct:
973 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200974 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100975 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100976 t.from_dict(abstract_data)
977 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100978 raise NotImplementedError(
979 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100980
Harald Weltec91085e2022-02-10 18:05:45 +0100981 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200982 """Encode abstract representation into raw (binary) data.
983
984 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
985 method for implementing this specifically for the given file. This function checks which
986 of the method exists, add calls them (with conversion, as needed).
987
988 Args:
989 abstract_data : dict representing the decoded data
990 Returns:
991 binary encoded data
992 """
Harald Welteb2edd142021-01-08 23:29:35 +0100993 method = getattr(self, '_encode_record_bin', None)
994 if callable(method):
995 return method(abstract_data)
996 method = getattr(self, '_encode_record_hex', None)
997 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +0200998 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200999 if self._construct:
1000 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +02001001 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001002 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001003 t.from_dict(abstract_data)
1004 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001005 raise NotImplementedError(
1006 "%s encoder not yet implemented. Patches welcome." % self)
1007
Harald Welteb2edd142021-01-08 23:29:35 +01001008
1009class CyclicEF(LinFixedEF):
1010 """Cyclic EF (Entry File) in the smart card filesystem"""
1011 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +01001012
1013 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001014 rec_len={1, None}, **kwargs):
1015 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +01001016
Harald Welteb2edd142021-01-08 23:29:35 +01001017
1018class TransRecEF(TransparentEF):
1019 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +02001020
Harald Welteb2edd142021-01-08 23:29:35 +01001021 These are the real odd-balls and mostly look like mistakes in the specification:
1022 Specified as 'transparent' EF, but actually containing several fixed-length records
1023 inside.
1024 We add a special class for those, so the user only has to provide encoder/decoder functions
1025 for a record, while this class takes care of split / merge of records.
1026 """
Harald Weltec91085e2022-02-10 18:05:45 +01001027
1028 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001029 parent: Optional[CardDF] = None, size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +02001030 """
1031 Args:
1032 fid : File Identifier (4 hex digits)
1033 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +02001034 name : Brief name of the file, like EF_ICCID
1035 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +02001036 parent : Parent CardFile object within filesystem hierarchy
1037 rec_len : Length of the fixed-length records within transparent EF
1038 size : tuple of (minimum_size, recommended_size)
1039 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001040 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +01001041 self.rec_len = rec_len
1042
Harald Weltec91085e2022-02-10 18:05:45 +01001043 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001044 """Decode raw (hex string) data into abstract representation.
1045
1046 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1047 method for implementing this specifically for the given file. This function checks which
1048 of the method exists, add calls them (with conversion, as needed).
1049
1050 Args:
1051 raw_hex_data : hex-encoded data
1052 Returns:
1053 abstract_data; dict representing the decoded data
1054 """
Harald Welteb2edd142021-01-08 23:29:35 +01001055 method = getattr(self, '_decode_record_hex', None)
1056 if callable(method):
1057 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001058 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001059 method = getattr(self, '_decode_record_bin', None)
1060 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001061 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001062 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001063 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001064 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001065 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001066 t.from_tlv(raw_bin_data)
1067 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001068 return {'raw': raw_hex_data}
1069
Harald Weltec91085e2022-02-10 18:05:45 +01001070 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001071 """Decode raw (binary) data into abstract representation.
1072
1073 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1074 method for implementing this specifically for the given file. This function checks which
1075 of the method exists, add calls them (with conversion, as needed).
1076
1077 Args:
1078 raw_bin_data : binary encoded data
1079 Returns:
1080 abstract_data; dict representing the decoded data
1081 """
Harald Welteb2edd142021-01-08 23:29:35 +01001082 method = getattr(self, '_decode_record_bin', None)
1083 if callable(method):
1084 return method(raw_bin_data)
1085 raw_hex_data = b2h(raw_bin_data)
1086 method = getattr(self, '_decode_record_hex', None)
1087 if callable(method):
1088 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001089 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001090 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001091 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001092 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001093 t.from_tlv(raw_bin_data)
1094 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001095 return {'raw': raw_hex_data}
1096
Harald Weltec91085e2022-02-10 18:05:45 +01001097 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001098 """Encode abstract representation into raw (hex string) data.
1099
1100 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1101 method for implementing this specifically for the given file. This function checks which
1102 of the method exists, add calls them (with conversion, as needed).
1103
1104 Args:
1105 abstract_data : dict representing the decoded data
1106 Returns:
1107 hex string encoded data
1108 """
Harald Welteb2edd142021-01-08 23:29:35 +01001109 method = getattr(self, '_encode_record_hex', None)
1110 if callable(method):
1111 return method(abstract_data)
1112 method = getattr(self, '_encode_record_bin', None)
1113 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001114 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001115 if self._construct:
1116 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001117 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001118 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001119 t.from_dict(abstract_data)
1120 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001121 raise NotImplementedError(
1122 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001123
Harald Weltec91085e2022-02-10 18:05:45 +01001124 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001125 """Encode abstract representation into raw (binary) data.
1126
1127 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1128 method for implementing this specifically for the given file. This function checks which
1129 of the method exists, add calls them (with conversion, as needed).
1130
1131 Args:
1132 abstract_data : dict representing the decoded data
1133 Returns:
1134 binary encoded data
1135 """
Harald Welteb2edd142021-01-08 23:29:35 +01001136 method = getattr(self, '_encode_record_bin', None)
1137 if callable(method):
1138 return method(abstract_data)
1139 method = getattr(self, '_encode_record_hex', None)
1140 if callable(method):
1141 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001142 if self._construct:
1143 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001144 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001145 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001146 t.from_dict(abstract_data)
1147 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001148 raise NotImplementedError(
1149 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001150
Harald Weltec91085e2022-02-10 18:05:45 +01001151 def _decode_bin(self, raw_bin_data: bytearray):
1152 chunks = [raw_bin_data[i:i+self.rec_len]
1153 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001154 return [self.decode_record_bin(x) for x in chunks]
1155
Harald Welteee3501f2021-04-02 13:00:18 +02001156 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001157 chunks = [self.encode_record_bin(x) for x in abstract_data]
1158 # FIXME: pad to file size
1159 return b''.join(chunks)
1160
1161
Harald Welte917d98c2021-04-21 11:51:25 +02001162class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001163 """BER-TLV EF (Entry File) in the smart card filesystem.
1164 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001165
Harald Welte27881622021-04-21 11:16:31 +02001166 NOTE: We currently don't really support those, this class is simply a wrapper
1167 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1168 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001169
Harald Welte917d98c2021-04-21 11:51:25 +02001170 @with_default_category('BER-TLV EF Commands')
1171 class ShellCommands(CommandSet):
1172 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001173
Harald Welte917d98c2021-04-21 11:51:25 +02001174 def __init__(self):
1175 super().__init__()
1176
1177 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001178 retrieve_data_parser.add_argument(
1179 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1180
Harald Welte917d98c2021-04-21 11:51:25 +02001181 @cmd2.with_argparser(retrieve_data_parser)
1182 def do_retrieve_data(self, opts):
1183 """Retrieve (Read) data from a BER-TLV EF"""
1184 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1185 self._cmd.poutput(data)
1186
1187 def do_retrieve_tags(self, opts):
1188 """List tags available in a given BER-TLV EF"""
1189 tags = self._cmd.rs.retrieve_tags()
1190 self._cmd.poutput(tags)
1191
1192 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001193 set_data_parser.add_argument(
1194 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1195 set_data_parser.add_argument(
1196 'data', help='Data bytes (hex format) to write')
1197
Harald Welte917d98c2021-04-21 11:51:25 +02001198 @cmd2.with_argparser(set_data_parser)
1199 def do_set_data(self, opts):
1200 """Set (Write) data for a given tag in a BER-TLV EF"""
1201 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1202 if data:
1203 self._cmd.poutput(data)
1204
1205 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001206 del_data_parser.add_argument(
1207 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1208
Harald Welte917d98c2021-04-21 11:51:25 +02001209 @cmd2.with_argparser(del_data_parser)
1210 def do_delete_data(self, opts):
1211 """Delete data for a given tag in a BER-TLV EF"""
1212 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1213 if data:
1214 self._cmd.poutput(data)
1215
Harald Weltec91085e2022-02-10 18:05:45 +01001216 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001217 size={1, None}, **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001218 """
1219 Args:
1220 fid : File Identifier (4 hex digits)
1221 sfid : Short File Identifier (2 hex digits, optional)
1222 name : Brief name of the file, lik EF_ICCID
1223 desc : Description of the file
1224 parent : Parent CardFile object within filesystem hierarchy
1225 size : tuple of (minimum_size, recommended_size)
1226 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001227 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001228 self._construct = None
1229 self.size = size
1230 self.shell_commands = [self.ShellCommands()]
1231
Harald Welteb2edd142021-01-08 23:29:35 +01001232
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001233class RuntimeState:
Harald Welteb2edd142021-01-08 23:29:35 +01001234 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001235
1236 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001237 """
1238 Args:
1239 card : pysim.cards.Card instance
1240 profile : CardProfile instance
1241 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001242 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001243 self.card = card
Harald Weltec91085e2022-02-10 18:05:45 +01001244 self.selected_file = self.mf # type: CardDF
Harald Welteb2edd142021-01-08 23:29:35 +01001245 self.profile = profile
Harald Welte2bb17f32022-02-15 15:41:55 +01001246 self.selected_file_fcp = None
1247 self.selected_file_fcp_hex = None
Philipp Maier51cad0d2021-11-08 15:45:10 +01001248
1249 # make sure the class and selection control bytes, which are specified
1250 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001251 self.card.set_apdu_parameter(
1252 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001253
Harald Welte5ce35242021-04-02 20:27:05 +02001254 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001255 apps = self._match_applications()
1256 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001257 if a.adf:
1258 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001259 for f in self.profile.files_in_mf:
1260 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001261 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001262
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001263 # make sure that when the runtime state is created, the card is also
1264 # in a defined state.
1265 self.reset()
1266
Philipp Maier1e896f32021-03-10 17:02:53 +01001267 def _match_applications(self):
1268 """match the applications from the profile with applications on the card"""
1269 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001270
1271 # When the profile does not feature any applications, then we are done already
1272 if not apps_profile:
1273 return []
1274
1275 # Read AIDs from card and match them against the applications defined by the
1276 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001277 aids_card = self.card.read_aids()
1278 apps_taken = []
1279 if aids_card:
1280 aids_taken = []
1281 print("AIDs on card:")
1282 for a in aids_card:
1283 for f in apps_profile:
1284 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001285 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001286 aids_taken.append(a)
1287 apps_taken.append(f)
1288 aids_unknown = set(aids_card) - set(aids_taken)
1289 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001290 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001291 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001292 print("warning: EF.DIR seems to be empty!")
1293
1294 # Some card applications may not be registered in EF.DIR, we will actively
1295 # probe for those applications
1296 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001297 try:
1298 data, sw = self.card.select_adf_by_aid(f.aid)
1299 if sw == "9000":
1300 print(" %s: %s" % (f.name, f.aid))
1301 apps_taken.append(f)
1302 except SwMatchError:
1303 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001304 return apps_taken
1305
Harald Welte747a9782022-02-13 17:52:28 +01001306 def selected_file_descriptor_byte(self) -> dict:
1307 return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
1308
1309 def selected_file_shareable(self) -> bool:
1310 return self.selected_file_descriptor_byte()['shareable']
1311
1312 def selected_file_structure(self) -> str:
1313 return self.selected_file_descriptor_byte()['structure']
1314
1315 def selected_file_type(self) -> str:
1316 return self.selected_file_descriptor_byte()['file_type']
1317
1318 def selected_file_num_of_rec(self) -> Optional[int]:
1319 return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
1320
Harald Weltedaf2b392021-05-03 23:17:29 +02001321 def reset(self, cmd_app=None) -> Hexstr:
1322 """Perform physical card reset and obtain ATR.
1323 Args:
1324 cmd_app : Command Application State (for unregistering old file commands)
1325 """
Philipp Maier946226a2021-10-29 18:31:03 +02001326 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001327 # select MF to reset internal state and to verify card really works
1328 self.select('MF', cmd_app)
1329 return atr
1330
Harald Welteee3501f2021-04-02 13:00:18 +02001331 def get_cwd(self) -> CardDF:
1332 """Obtain the current working directory.
1333
1334 Returns:
1335 CardDF instance
1336 """
Harald Welteb2edd142021-01-08 23:29:35 +01001337 if isinstance(self.selected_file, CardDF):
1338 return self.selected_file
1339 else:
1340 return self.selected_file.parent
1341
Harald Welte5ce35242021-04-02 20:27:05 +02001342 def get_application_df(self) -> Optional[CardADF]:
1343 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001344
1345 Returns:
1346 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001347 # iterate upwards from selected file; check if any is an ADF
1348 node = self.selected_file
1349 while node.parent != node:
1350 if isinstance(node, CardADF):
1351 return node
1352 node = node.parent
1353 return None
1354
Harald Weltec91085e2022-02-10 18:05:45 +01001355 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001356 """Interpret a given status word relative to the currently selected application
1357 or the underlying card profile.
1358
1359 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001360 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001361
1362 Returns:
1363 Tuple of two strings
1364 """
Harald Welte86fbd392021-04-02 22:13:09 +02001365 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001366 adf = self.get_application_df()
1367 if adf:
1368 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001369 # The application either comes with its own interpret_sw
1370 # method or we will use the interpret_sw method from the
1371 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001372 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001373 res = app.interpret_sw(sw)
1374 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001375
Harald Weltec91085e2022-02-10 18:05:45 +01001376 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001377 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001378 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001379 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001380 raise ValueError(
1381 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001382
1383 try:
1384 (data, sw) = self.card._scc.select_file(fid)
1385 except SwMatchError as swm:
1386 k = self.interpret_sw(swm.sw_actual)
1387 if not k:
1388 raise(swm)
1389 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1390
1391 select_resp = self.selected_file.decode_select_response(data)
Harald Welte747a9782022-02-13 17:52:28 +01001392 if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001393 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1394 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001395 else:
Harald Welte747a9782022-02-13 17:52:28 +01001396 if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001397 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1398 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001399 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001400 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1401 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001402
1403 self.selected_file.add_files([f])
1404 self.selected_file = f
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001405 return select_resp, data
Philipp Maier63f572d2021-03-09 22:42:47 +01001406
Harald Welteaceb2a52022-02-12 21:41:59 +01001407 def _select_pre(self, cmd_app):
1408 # unregister commands of old file
1409 if cmd_app and self.selected_file.shell_commands:
1410 for c in self.selected_file.shell_commands:
1411 cmd_app.unregister_command_set(c)
1412
1413 def _select_post(self, cmd_app):
1414 # register commands of new file
1415 if cmd_app and self.selected_file.shell_commands:
1416 for c in self.selected_file.shell_commands:
1417 cmd_app.register_command_set(c)
1418
1419 def select_file(self, file: CardFile, cmd_app=None):
1420 """Select a file (EF, DF, ADF, MF, ...).
1421
1422 Args:
1423 file : CardFile [or derived class] instance
1424 cmd_app : Command Application State (for unregistering old file commands)
1425 """
1426 # we need to find a path from our self.selected_file to the destination
1427 inter_path = self.selected_file.build_select_path_to(file)
1428 if not inter_path:
1429 raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
1430
1431 self._select_pre(cmd_app)
1432
1433 for p in inter_path:
1434 try:
1435 if isinstance(p, CardADF):
1436 (data, sw) = self.card.select_adf_by_aid(p.aid)
1437 else:
1438 (data, sw) = self.card._scc.select_file(p.fid)
1439 self.selected_file = p
1440 except SwMatchError as swm:
1441 self._select_post(cmd_app)
1442 raise(swm)
1443
1444 self._select_post(cmd_app)
1445
Harald Weltec91085e2022-02-10 18:05:45 +01001446 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001447 """Select a file (EF, DF, ADF, MF, ...).
1448
1449 Args:
1450 name : Name of file to select
1451 cmd_app : Command Application State (for unregistering old file commands)
1452 """
Harald Welteee670bc2022-02-13 15:10:15 +01001453 # handling of entire paths with multiple directories/elements
1454 if '/' in name:
1455 prev_sel_file = self.selected_file
1456 pathlist = name.split('/')
1457 # treat /DF.GSM/foo like MF/DF.GSM/foo
1458 if pathlist[0] == '':
1459 pathlist[0] = 'MF'
1460 try:
1461 for p in pathlist:
1462 self.select(p, cmd_app)
1463 return
1464 except Exception as e:
1465 # if any intermediate step fails, go back to where we were
1466 self.select_file(prev_sel_file, cmd_app)
1467 raise e
1468
Harald Welteb2edd142021-01-08 23:29:35 +01001469 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001470 if is_hex(name):
1471 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001472
Harald Welteaceb2a52022-02-12 21:41:59 +01001473 self._select_pre(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001474
Harald Welteb2edd142021-01-08 23:29:35 +01001475 if name in sels:
1476 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001477 try:
1478 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001479 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001480 else:
1481 (data, sw) = self.card._scc.select_file(f.fid)
1482 self.selected_file = f
1483 except SwMatchError as swm:
1484 k = self.interpret_sw(swm.sw_actual)
1485 if not k:
1486 raise(swm)
1487 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001488 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001489 else:
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001490 (select_resp, data) = self.probe_file(name, cmd_app)
1491
Harald Welte2bb17f32022-02-15 15:41:55 +01001492 # store the raw + decoded FCP for later reference
1493 self.selected_file_fcp_hex = data
Harald Welte850b72a2021-04-07 09:33:03 +02001494 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001495
Harald Welteaceb2a52022-02-12 21:41:59 +01001496 self._select_post(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001497 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001498
Harald Welte34b05d32021-05-25 22:03:13 +02001499 def status(self):
1500 """Request STATUS (current selected file FCP) from card."""
1501 (data, sw) = self.card._scc.status()
1502 return self.selected_file.decode_select_response(data)
1503
Harald Welte3c9b7842021-10-19 21:44:24 +02001504 def get_file_for_selectable(self, name: str):
1505 sels = self.selected_file.get_selectables()
1506 return sels[name]
1507
Harald Weltec91085e2022-02-10 18:05:45 +01001508 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001509 """Request ACTIVATE FILE of specified file."""
1510 sels = self.selected_file.get_selectables()
1511 f = sels[name]
1512 data, sw = self.card._scc.activate_file(f.fid)
1513 return data, sw
1514
Harald Weltec91085e2022-02-10 18:05:45 +01001515 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001516 """Read [part of] a transparent EF binary data.
1517
1518 Args:
1519 length : Amount of data to read (None: as much as possible)
1520 offset : Offset into the file from which to read 'length' bytes
1521 Returns:
1522 binary data read from the file
1523 """
Harald Welteb2edd142021-01-08 23:29:35 +01001524 if not isinstance(self.selected_file, TransparentEF):
1525 raise TypeError("Only works with TransparentEF")
1526 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1527
Harald Welte2d4a64b2021-04-03 09:01:24 +02001528 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001529 """Read [part of] a transparent EF binary data and decode it.
1530
1531 Args:
1532 length : Amount of data to read (None: as much as possible)
1533 offset : Offset into the file from which to read 'length' bytes
1534 Returns:
1535 abstract decode data read from the file
1536 """
Harald Welteb2edd142021-01-08 23:29:35 +01001537 (data, sw) = self.read_binary()
1538 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001539 return (dec_data, sw)
1540
Harald Weltec91085e2022-02-10 18:05:45 +01001541 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001542 """Update transparent EF binary data.
1543
1544 Args:
1545 data_hex : hex string of data to be written
1546 offset : Offset into the file from which to write 'data_hex'
1547 """
Harald Welteb2edd142021-01-08 23:29:35 +01001548 if not isinstance(self.selected_file, TransparentEF):
1549 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001550 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001551
Harald Weltec91085e2022-02-10 18:05:45 +01001552 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001553 """Update transparent EF from abstract data. Encodes the data to binary and
1554 then updates the EF with it.
1555
1556 Args:
1557 data : abstract data which is to be encoded and written
1558 """
Harald Welteb2edd142021-01-08 23:29:35 +01001559 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001560 return self.update_binary(data_hex)
1561
Harald Weltec91085e2022-02-10 18:05:45 +01001562 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001563 """Read a record as binary data.
1564
1565 Args:
1566 rec_nr : Record number to read
1567 Returns:
1568 hex string of binary data contained in record
1569 """
Harald Welteb2edd142021-01-08 23:29:35 +01001570 if not isinstance(self.selected_file, LinFixedEF):
1571 raise TypeError("Only works with Linear Fixed EF")
1572 # returns a string of hex nibbles
1573 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1574
Harald Weltec91085e2022-02-10 18:05:45 +01001575 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001576 """Read a record and decode it to abstract data.
1577
1578 Args:
1579 rec_nr : Record number to read
1580 Returns:
1581 abstract data contained in record
1582 """
Harald Welteb2edd142021-01-08 23:29:35 +01001583 (data, sw) = self.read_record(rec_nr)
1584 return (self.selected_file.decode_record_hex(data), sw)
1585
Harald Weltec91085e2022-02-10 18:05:45 +01001586 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001587 """Update a record with given binary data
1588
1589 Args:
1590 rec_nr : Record number to read
1591 data_hex : Hex string binary data to be written
1592 """
Harald Welteb2edd142021-01-08 23:29:35 +01001593 if not isinstance(self.selected_file, LinFixedEF):
1594 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001595 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 +01001596
Harald Weltec91085e2022-02-10 18:05:45 +01001597 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001598 """Update a record with given abstract data. Will encode abstract to binary data
1599 and then write it to the given record on the card.
1600
1601 Args:
1602 rec_nr : Record number to read
1603 data_hex : Abstract data to be written
1604 """
Harald Welte1e456572021-04-02 17:16:30 +02001605 data_hex = self.selected_file.encode_record_hex(data)
1606 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001607
Harald Weltec91085e2022-02-10 18:05:45 +01001608 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001609 """Read a DO/TLV as binary data.
1610
1611 Args:
1612 tag : Tag of TLV/DO to read
1613 Returns:
1614 hex string of full BER-TLV DO including Tag and Length
1615 """
1616 if not isinstance(self.selected_file, BerTlvEF):
1617 raise TypeError("Only works with BER-TLV EF")
1618 # returns a string of hex nibbles
1619 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1620
1621 def retrieve_tags(self):
1622 """Retrieve tags available on BER-TLV EF.
1623
1624 Returns:
1625 list of integer tags contained in EF
1626 """
1627 if not isinstance(self.selected_file, BerTlvEF):
1628 raise TypeError("Only works with BER-TLV EF")
1629 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001630 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001631 return list(value)
1632
Harald Weltec91085e2022-02-10 18:05:45 +01001633 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001634 """Update a TLV/DO with given binary data
1635
1636 Args:
1637 tag : Tag of TLV/DO to be written
1638 data_hex : Hex string binary data to be written (value portion)
1639 """
1640 if not isinstance(self.selected_file, BerTlvEF):
1641 raise TypeError("Only works with BER-TLV EF")
1642 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1643
Philipp Maier5d698e52021-09-16 13:18:01 +02001644 def unregister_cmds(self, cmd_app=None):
1645 """Unregister all file specific commands."""
1646 if cmd_app and self.selected_file.shell_commands:
1647 for c in self.selected_file.shell_commands:
1648 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001649
Harald Welteb2edd142021-01-08 23:29:35 +01001650
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001651class FileData:
Harald Welteb2edd142021-01-08 23:29:35 +01001652 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001653
Harald Welteb2edd142021-01-08 23:29:35 +01001654 def __init__(self, fdesc):
1655 self.desc = fdesc
1656 self.fcp = None
1657
1658
Harald Weltec91085e2022-02-10 18:05:45 +01001659def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001660 """Interpret a given status word.
1661
1662 Args:
1663 sw_data : Hierarchical dict of status word matches
1664 sw : status word to match (string of 4 hex digits)
1665 Returns:
1666 tuple of two strings (class_string, description)
1667 """
Harald Welteb2edd142021-01-08 23:29:35 +01001668 for class_str, swdict in sw_data.items():
1669 # first try direct match
1670 if sw in swdict:
1671 return (class_str, swdict[sw])
1672 # next try wildcard matches
1673 for pattern, descr in swdict.items():
1674 if sw_match(sw, pattern):
1675 return (class_str, descr)
1676 return None
1677
Harald Weltec91085e2022-02-10 18:05:45 +01001678
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001679class CardApplication:
Harald Welteb2edd142021-01-08 23:29:35 +01001680 """A card application is represented by an ADF (with contained hierarchy) and optionally
1681 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001682
1683 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001684 """
1685 Args:
1686 adf : ADF name
1687 sw : Dict of status word conversions
1688 """
Harald Welteb2edd142021-01-08 23:29:35 +01001689 self.name = name
1690 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001691 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001692 # back-reference from ADF to Applicaiton
1693 if self.adf:
1694 self.aid = aid or self.adf.aid
1695 self.adf.application = self
1696 else:
1697 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001698
1699 def __str__(self):
1700 return "APP(%s)" % (self.name)
1701
1702 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001703 """Interpret a given status word within the application.
1704
1705 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001706 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001707
1708 Returns:
1709 Tuple of two strings
1710 """
Harald Welteb2edd142021-01-08 23:29:35 +01001711 return interpret_sw(self.sw, sw)
1712
Harald Weltef44256c2021-10-14 15:53:39 +02001713
1714class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001715 """A specific card model, typically having some additional vendor-specific files. All
1716 you need to do is to define a sub-class with a list of ATRs or an overridden match
1717 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001718 _atrs = []
1719
1720 @classmethod
1721 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001722 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001723 """Add model specific files to given RuntimeState."""
1724
1725 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001726 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001727 """Test if given card matches this model."""
1728 card_atr = scc.get_atr()
1729 for atr in cls._atrs:
1730 atr_bin = toBytes(atr)
1731 if atr_bin == card_atr:
1732 print("Detected CardModel:", cls.__name__)
1733 return True
1734 return False
1735
1736 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001737 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001738 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1739 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1740 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001741 for m in CardModel.__subclasses__():
1742 if m.match(scc):
1743 m.add_files(rs)