blob: 813bebb4a16d7b662f59a5517ac608c7129625e4 [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 Welted56f45d2022-07-16 11:46:59 +0200304 def _has_service(self):
305 if self.service:
306 return True
307 for c in self.children.values():
308 if isinstance(c, CardDF):
309 if c._has_service():
310 return True
311
Harald Weltec91085e2022-02-10 18:05:45 +0100312 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200313 """Add a child (DF/EF) to this DF.
314 Args:
315 child: The new DF/EF to be added
316 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
317 """
Harald Welteb2edd142021-01-08 23:29:35 +0100318 if not isinstance(child, CardFile):
319 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100320 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100321 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100322 if child.name in CardFile.RESERVED_NAMES:
323 raise ValueError("File name %s is a reserved name" % (child.name))
324 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100325 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100326 if child.fid in self.children:
327 if ignore_existing:
328 return
Harald Weltec91085e2022-02-10 18:05:45 +0100329 raise ValueError(
330 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100331 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100332 raise ValueError(
333 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100334 if self.lookup_file_by_name(child.name):
335 if ignore_existing:
336 return
Harald Weltec91085e2022-02-10 18:05:45 +0100337 raise ValueError(
338 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100339 self.children[child.fid] = child
340 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100341 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100342 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100343 if isinstance(child, CardDF):
344 for c in child.children.values():
345 self._add_file_services(c)
346 if isinstance(c, CardDF):
Harald Welted56f45d2022-07-16 11:46:59 +0200347 for gc in c.children.values():
348 if isinstance(gc, CardDF):
349 if gc._has_service():
350 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100351
Harald Weltec91085e2022-02-10 18:05:45 +0100352 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200353 """Add a list of child (DF/EF) to this DF
354
355 Args:
356 children: List of new DF/EFs to be added
357 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
358 """
Harald Welteb2edd142021-01-08 23:29:35 +0100359 for child in children:
360 self.add_file(child, ignore_existing)
361
Harald Weltec91085e2022-02-10 18:05:45 +0100362 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200363 """Return a dict of {'identifier': File} that is selectable from the current DF.
364
365 Args:
366 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
367 If not specified, all selectables will be returned.
368 Returns:
369 dict containing all selectable items. Key is identifier (string), value
370 a reference to a CardFile (or derived class) instance.
371 """
Harald Welteb2edd142021-01-08 23:29:35 +0100372 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100373 sels = super().get_selectables(flags)
374 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100375 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100376 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100377 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100378 return sels
379
Harald Weltec91085e2022-02-10 18:05:45 +0100380 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200381 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100382 if name == None:
383 return None
384 for i in self.children.values():
385 if i.name and i.name == name:
386 return i
387 return None
388
Harald Weltec91085e2022-02-10 18:05:45 +0100389 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200390 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100391 if sfid == None:
392 return None
393 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200394 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100395 return i
396 return None
397
Harald Weltec91085e2022-02-10 18:05:45 +0100398 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200399 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100400 if fid in self.children:
401 return self.children[fid]
402 return None
403
404
405class CardMF(CardDF):
406 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100407
Harald Welteb2edd142021-01-08 23:29:35 +0100408 def __init__(self, **kwargs):
409 # can be overridden; use setdefault
410 kwargs.setdefault('fid', '3f00')
411 kwargs.setdefault('name', 'MF')
412 kwargs.setdefault('desc', 'Master File (directory root)')
413 # cannot be overridden; use assignment
414 kwargs['parent'] = self
415 super().__init__(**kwargs)
416 self.applications = dict()
417
418 def __str__(self):
419 return "MF(%s)" % (self.fid)
420
Harald Weltec91085e2022-02-10 18:05:45 +0100421 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200422 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100423 if not isinstance(app, CardADF):
424 raise TypeError("Expected an ADF instance")
425 if app.aid in self.applications:
426 raise ValueError("AID %s already exists" % (app.aid))
427 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100428 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100429
430 def get_app_names(self):
431 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100432 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100433
Harald Weltec91085e2022-02-10 18:05:45 +0100434 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200435 """Return a dict of {'identifier': File} that is selectable from the current DF.
436
437 Args:
438 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
439 If not specified, all selectables will be returned.
440 Returns:
441 dict containing all selectable items. Key is identifier (string), value
442 a reference to a CardFile (or derived class) instance.
443 """
Philipp Maier786f7812021-02-25 16:48:10 +0100444 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100445 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100446 return sels
447
Harald Weltec91085e2022-02-10 18:05:45 +0100448 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100449 """Get applications by AID + name"""
450 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100451 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100452 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100453 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100454 sels.update(
455 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100456 return sels
457
Harald Weltec9752512022-02-11 16:31:15 +0100458 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200459 """Decode the response to a SELECT command.
460
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100461 This is the fall-back method which automatically defers to the standard decoding
462 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100463 performed. Specific derived classes (usually ADF) can overload this method to
464 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200465 """
Harald Welteb2edd142021-01-08 23:29:35 +0100466
Harald Weltec9752512022-02-11 16:31:15 +0100467 if not data_hex:
468 return data_hex
469
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100470 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100471
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100472 if profile:
473 return profile.decode_select_response(data_hex)
474 else:
475 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100476
Harald Weltec91085e2022-02-10 18:05:45 +0100477
Harald Welteb2edd142021-01-08 23:29:35 +0100478class CardADF(CardDF):
479 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100480
481 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100482 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200483 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200484 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100485 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200486 mf = self.get_mf()
487 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200488 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100489
490 def __str__(self):
491 return "ADF(%s)" % (self.aid)
492
Harald Weltec91085e2022-02-10 18:05:45 +0100493 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100494 if self.name and prefer_name:
495 return self.name
496 else:
497 return self.aid
498
499
500class CardEF(CardFile):
501 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100502
Harald Welteb2edd142021-01-08 23:29:35 +0100503 def __init__(self, *, fid, **kwargs):
504 kwargs['fid'] = fid
505 super().__init__(**kwargs)
506
507 def __str__(self):
508 return "EF(%s)" % (super().__str__())
509
Harald Weltec91085e2022-02-10 18:05:45 +0100510 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200511 """Return a dict of {'identifier': File} that is selectable from the current DF.
512
513 Args:
514 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
515 If not specified, all selectables will be returned.
516 Returns:
517 dict containing all selectable items. Key is identifier (string), value
518 a reference to a CardFile (or derived class) instance.
519 """
Harald Weltec91085e2022-02-10 18:05:45 +0100520 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100521 sels = super().get_selectables(flags)
Harald Weltec91085e2022-02-10 18:05:45 +0100522 sels.update(
523 {x.name: x for x in self.parent.children.values() if x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100524 return sels
525
526
527class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200528 """Transparent EF (Entry File) in the smart card filesystem.
529
530 A Transparent EF is a binary file with no formal structure. This is contrary to
531 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100532
533 @with_default_category('Transparent EF Commands')
534 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200535 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100536
Harald Welteb2edd142021-01-08 23:29:35 +0100537 def __init__(self):
538 super().__init__()
539
Harald Welteaefd0642022-02-25 15:26:37 +0100540 dec_hex_parser = argparse.ArgumentParser()
541 dec_hex_parser.add_argument('--oneline', action='store_true',
542 help='No JSON pretty-printing, dump as a single line')
543 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
544
545 @cmd2.with_argparser(dec_hex_parser)
546 def do_decode_hex(self, opts):
547 """Decode command-line provided hex-string as if it was read from the file."""
548 data = self._cmd.rs.selected_file.decode_hex(opts.HEXSTR)
549 self._cmd.poutput_json(data, opts.oneline)
550
Harald Welteb2edd142021-01-08 23:29:35 +0100551 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100552 read_bin_parser.add_argument(
553 '--offset', type=int, default=0, help='Byte offset for start of read')
554 read_bin_parser.add_argument(
555 '--length', type=int, help='Number of bytes to read')
556
Harald Welteb2edd142021-01-08 23:29:35 +0100557 @cmd2.with_argparser(read_bin_parser)
558 def do_read_binary(self, opts):
559 """Read binary data from a transparent EF"""
560 (data, sw) = self._cmd.rs.read_binary(opts.length, opts.offset)
561 self._cmd.poutput(data)
562
Harald Weltebcad86c2021-04-06 20:08:39 +0200563 read_bin_dec_parser = argparse.ArgumentParser()
564 read_bin_dec_parser.add_argument('--oneline', action='store_true',
565 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100566
Harald Weltebcad86c2021-04-06 20:08:39 +0200567 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100568 def do_read_binary_decoded(self, opts):
569 """Read + decode data from a transparent EF"""
570 (data, sw) = self._cmd.rs.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200571 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100572
573 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100574 upd_bin_parser.add_argument(
575 '--offset', type=int, default=0, help='Byte offset for start of read')
576 upd_bin_parser.add_argument(
577 'data', help='Data bytes (hex format) to write')
578
Harald Welteb2edd142021-01-08 23:29:35 +0100579 @cmd2.with_argparser(upd_bin_parser)
580 def do_update_binary(self, opts):
581 """Update (Write) data of a transparent EF"""
582 (data, sw) = self._cmd.rs.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100583 if data:
584 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100585
586 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100587 upd_bin_dec_parser.add_argument(
588 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200589 upd_bin_dec_parser.add_argument('--json-path', type=str,
590 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100591
Harald Welteb2edd142021-01-08 23:29:35 +0100592 @cmd2.with_argparser(upd_bin_dec_parser)
593 def do_update_binary_decoded(self, opts):
594 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200595 if opts.json_path:
596 (data_json, sw) = self._cmd.rs.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100597 js_path_modify(data_json, opts.json_path,
598 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200599 else:
600 data_json = json.loads(opts.data)
Harald Welteb2edd142021-01-08 23:29:35 +0100601 (data, sw) = self._cmd.rs.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100602 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200603 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100604
Harald Welte4145d3c2021-04-08 20:34:13 +0200605 def do_edit_binary_decoded(self, opts):
606 """Edit the JSON representation of the EF contents in an editor."""
607 (orig_json, sw) = self._cmd.rs.read_binary_dec()
608 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
609 filename = '%s/file' % dirname
610 # write existing data as JSON to file
611 with open(filename, 'w') as text_file:
612 json.dump(orig_json, text_file, indent=4)
613 # run a text editor
614 self._cmd._run_editor(filename)
615 with open(filename, 'r') as text_file:
616 edited_json = json.load(text_file)
617 if edited_json == orig_json:
618 self._cmd.poutput("Data not modified, skipping write")
619 else:
620 (data, sw) = self._cmd.rs.update_binary_dec(edited_json)
621 if data:
622 self._cmd.poutput_json(data)
623
Harald Weltec91085e2022-02-10 18:05:45 +0100624 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100625 size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200626 """
627 Args:
628 fid : File Identifier (4 hex digits)
629 sfid : Short File Identifier (2 hex digits, optional)
630 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200631 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200632 parent : Parent CardFile object within filesystem hierarchy
633 size : tuple of (minimum_size, recommended_size)
634 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100635 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200636 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200637 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100638 self.size = size
639 self.shell_commands = [self.ShellCommands()]
640
Harald Weltec91085e2022-02-10 18:05:45 +0100641 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200642 """Decode raw (binary) data into abstract representation.
643
644 A derived class would typically provide a _decode_bin() or _decode_hex() method
645 for implementing this specifically for the given file. This function checks which
646 of the method exists, add calls them (with conversion, as needed).
647
648 Args:
649 raw_bin_data : binary encoded data
650 Returns:
651 abstract_data; dict representing the decoded data
652 """
Harald Welteb2edd142021-01-08 23:29:35 +0100653 method = getattr(self, '_decode_bin', None)
654 if callable(method):
655 return method(raw_bin_data)
656 method = getattr(self, '_decode_hex', None)
657 if callable(method):
658 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200659 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200660 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200661 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100662 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100663 t.from_tlv(raw_bin_data)
664 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100665 return {'raw': raw_bin_data.hex()}
666
Harald Weltec91085e2022-02-10 18:05:45 +0100667 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200668 """Decode raw (hex string) data into abstract representation.
669
670 A derived class would typically provide a _decode_bin() or _decode_hex() method
671 for implementing this specifically for the given file. This function checks which
672 of the method exists, add calls them (with conversion, as needed).
673
674 Args:
675 raw_hex_data : hex-encoded data
676 Returns:
677 abstract_data; dict representing the decoded data
678 """
Harald Welteb2edd142021-01-08 23:29:35 +0100679 method = getattr(self, '_decode_hex', None)
680 if callable(method):
681 return method(raw_hex_data)
682 raw_bin_data = h2b(raw_hex_data)
683 method = getattr(self, '_decode_bin', None)
684 if callable(method):
685 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200686 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200687 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200688 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100689 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100690 t.from_tlv(raw_bin_data)
691 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100692 return {'raw': raw_bin_data.hex()}
693
Harald Weltec91085e2022-02-10 18:05:45 +0100694 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200695 """Encode abstract representation into raw (binary) data.
696
697 A derived class would typically provide an _encode_bin() or _encode_hex() method
698 for implementing this specifically for the given file. This function checks which
699 of the method exists, add calls them (with conversion, as needed).
700
701 Args:
702 abstract_data : dict representing the decoded data
703 Returns:
704 binary encoded data
705 """
Harald Welteb2edd142021-01-08 23:29:35 +0100706 method = getattr(self, '_encode_bin', None)
707 if callable(method):
708 return method(abstract_data)
709 method = getattr(self, '_encode_hex', None)
710 if callable(method):
711 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200712 if self._construct:
713 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200714 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100715 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100716 t.from_dict(abstract_data)
717 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100718 raise NotImplementedError(
719 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100720
Harald Weltec91085e2022-02-10 18:05:45 +0100721 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200722 """Encode abstract representation into raw (hex string) data.
723
724 A derived class would typically provide an _encode_bin() or _encode_hex() method
725 for implementing this specifically for the given file. This function checks which
726 of the method exists, add calls them (with conversion, as needed).
727
728 Args:
729 abstract_data : dict representing the decoded data
730 Returns:
731 hex string encoded data
732 """
Harald Welteb2edd142021-01-08 23:29:35 +0100733 method = getattr(self, '_encode_hex', None)
734 if callable(method):
735 return method(abstract_data)
736 method = getattr(self, '_encode_bin', None)
737 if callable(method):
738 raw_bin_data = method(abstract_data)
739 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200740 if self._construct:
741 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200742 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100743 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100744 t.from_dict(abstract_data)
745 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100746 raise NotImplementedError(
747 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100748
749
750class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200751 """Linear Fixed EF (Entry File) in the smart card filesystem.
752
753 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
754 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100755
756 @with_default_category('Linear Fixed EF Commands')
757 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200758 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100759
Harald Welte9170fbf2022-02-11 21:54:37 +0100760 def __init__(self, **kwargs):
761 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100762
Harald Welteaefd0642022-02-25 15:26:37 +0100763 dec_hex_parser = argparse.ArgumentParser()
764 dec_hex_parser.add_argument('--oneline', action='store_true',
765 help='No JSON pretty-printing, dump as a single line')
766 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
767
768 @cmd2.with_argparser(dec_hex_parser)
769 def do_decode_hex(self, opts):
770 """Decode command-line provided hex-string as if it was read from the file."""
771 data = self._cmd.rs.selected_file.decode_record_hex(opts.HEXSTR)
772 self._cmd.poutput_json(data, opts.oneline)
773
Harald Welteb2edd142021-01-08 23:29:35 +0100774 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100775 read_rec_parser.add_argument(
776 'record_nr', type=int, help='Number of record to be read')
777 read_rec_parser.add_argument(
778 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
779
Harald Welteb2edd142021-01-08 23:29:35 +0100780 @cmd2.with_argparser(read_rec_parser)
781 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100782 """Read one or multiple records from a record-oriented EF"""
783 for r in range(opts.count):
784 recnr = opts.record_nr + r
785 (data, sw) = self._cmd.rs.read_record(recnr)
786 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100787 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100788 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100789 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100790 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100791
792 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100793 read_rec_dec_parser.add_argument(
794 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200795 read_rec_dec_parser.add_argument('--oneline', action='store_true',
796 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100797
Harald Welteb2edd142021-01-08 23:29:35 +0100798 @cmd2.with_argparser(read_rec_dec_parser)
799 def do_read_record_decoded(self, opts):
800 """Read + decode a record from a record-oriented EF"""
801 (data, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200802 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100803
Harald Welte850b72a2021-04-07 09:33:03 +0200804 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100805
Harald Welte850b72a2021-04-07 09:33:03 +0200806 @cmd2.with_argparser(read_recs_parser)
807 def do_read_records(self, opts):
808 """Read all records from a record-oriented EF"""
Harald Welte747a9782022-02-13 17:52:28 +0100809 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200810 for recnr in range(1, 1 + num_of_rec):
811 (data, sw) = self._cmd.rs.read_record(recnr)
812 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100813 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200814 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100815 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200816 self._cmd.poutput("%03d %s" % (recnr, recstr))
817
818 read_recs_dec_parser = argparse.ArgumentParser()
819 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100820 help='No JSON pretty-printing, dump as a single line')
821
Harald Welte850b72a2021-04-07 09:33:03 +0200822 @cmd2.with_argparser(read_recs_dec_parser)
823 def do_read_records_decoded(self, opts):
824 """Read + decode all records from a record-oriented EF"""
Harald Welte747a9782022-02-13 17:52:28 +0100825 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200826 # collect all results in list so they are rendered as JSON list when printing
827 data_list = []
828 for recnr in range(1, 1 + num_of_rec):
829 (data, sw) = self._cmd.rs.read_record_dec(recnr)
830 data_list.append(data)
831 self._cmd.poutput_json(data_list, opts.oneline)
832
Harald Welteb2edd142021-01-08 23:29:35 +0100833 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100834 upd_rec_parser.add_argument(
835 'record_nr', type=int, help='Number of record to be read')
836 upd_rec_parser.add_argument(
837 'data', help='Data bytes (hex format) to write')
838
Harald Welteb2edd142021-01-08 23:29:35 +0100839 @cmd2.with_argparser(upd_rec_parser)
840 def do_update_record(self, opts):
841 """Update (write) data to a record-oriented EF"""
842 (data, sw) = self._cmd.rs.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100843 if data:
844 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100845
846 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100847 upd_rec_dec_parser.add_argument(
848 'record_nr', type=int, help='Number of record to be read')
849 upd_rec_dec_parser.add_argument(
850 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200851 upd_rec_dec_parser.add_argument('--json-path', type=str,
852 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100853
Harald Welteb2edd142021-01-08 23:29:35 +0100854 @cmd2.with_argparser(upd_rec_dec_parser)
855 def do_update_record_decoded(self, opts):
856 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200857 if opts.json_path:
858 (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100859 js_path_modify(data_json, opts.json_path,
860 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200861 else:
862 data_json = json.loads(opts.data)
Harald Weltec91085e2022-02-10 18:05:45 +0100863 (data, sw) = self._cmd.rs.update_record_dec(
864 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100865 if data:
866 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100867
Harald Welte4145d3c2021-04-08 20:34:13 +0200868 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100869 edit_rec_dec_parser.add_argument(
870 'record_nr', type=int, help='Number of record to be edited')
871
Harald Welte4145d3c2021-04-08 20:34:13 +0200872 @cmd2.with_argparser(edit_rec_dec_parser)
873 def do_edit_record_decoded(self, opts):
874 """Edit the JSON representation of one record in an editor."""
875 (orig_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200876 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200877 filename = '%s/file' % dirname
878 # write existing data as JSON to file
879 with open(filename, 'w') as text_file:
880 json.dump(orig_json, text_file, indent=4)
881 # run a text editor
882 self._cmd._run_editor(filename)
883 with open(filename, 'r') as text_file:
884 edited_json = json.load(text_file)
885 if edited_json == orig_json:
886 self._cmd.poutput("Data not modified, skipping write")
887 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100888 (data, sw) = self._cmd.rs.update_record_dec(
889 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200890 if data:
891 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200892
Harald Weltec91085e2022-02-10 18:05:45 +0100893 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100894 parent: Optional[CardDF] = None, rec_len={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200895 """
896 Args:
897 fid : File Identifier (4 hex digits)
898 sfid : Short File Identifier (2 hex digits, optional)
899 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200900 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200901 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200902 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200903 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100904 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100905 self.rec_len = rec_len
906 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200907 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200908 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100909
Harald Weltec91085e2022-02-10 18:05:45 +0100910 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200911 """Decode raw (hex string) data into abstract representation.
912
913 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
914 method for implementing this specifically for the given file. This function checks which
915 of the method exists, add calls them (with conversion, as needed).
916
917 Args:
918 raw_hex_data : hex-encoded data
919 Returns:
920 abstract_data; dict representing the decoded data
921 """
Harald Welteb2edd142021-01-08 23:29:35 +0100922 method = getattr(self, '_decode_record_hex', None)
923 if callable(method):
924 return method(raw_hex_data)
925 raw_bin_data = h2b(raw_hex_data)
926 method = getattr(self, '_decode_record_bin', None)
927 if callable(method):
928 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200929 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200930 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200931 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100932 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100933 t.from_tlv(raw_bin_data)
934 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100935 return {'raw': raw_bin_data.hex()}
936
Harald Weltec91085e2022-02-10 18:05:45 +0100937 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200938 """Decode raw (binary) data into abstract representation.
939
940 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
941 method for implementing this specifically for the given file. This function checks which
942 of the method exists, add calls them (with conversion, as needed).
943
944 Args:
945 raw_bin_data : binary encoded data
946 Returns:
947 abstract_data; dict representing the decoded data
948 """
Harald Welteb2edd142021-01-08 23:29:35 +0100949 method = getattr(self, '_decode_record_bin', None)
950 if callable(method):
951 return method(raw_bin_data)
952 raw_hex_data = b2h(raw_bin_data)
953 method = getattr(self, '_decode_record_hex', None)
954 if callable(method):
955 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200956 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200957 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200958 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100959 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100960 t.from_tlv(raw_bin_data)
961 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100962 return {'raw': raw_hex_data}
963
Harald Weltec91085e2022-02-10 18:05:45 +0100964 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200965 """Encode abstract representation into raw (hex string) data.
966
967 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
968 method for implementing this specifically for the given file. This function checks which
969 of the method exists, add calls them (with conversion, as needed).
970
971 Args:
972 abstract_data : dict representing the decoded data
973 Returns:
974 hex string encoded data
975 """
Harald Welteb2edd142021-01-08 23:29:35 +0100976 method = getattr(self, '_encode_record_hex', None)
977 if callable(method):
978 return method(abstract_data)
979 method = getattr(self, '_encode_record_bin', None)
980 if callable(method):
981 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200982 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200983 if self._construct:
984 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200985 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100986 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100987 t.from_dict(abstract_data)
988 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100989 raise NotImplementedError(
990 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100991
Harald Weltec91085e2022-02-10 18:05:45 +0100992 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200993 """Encode abstract representation into raw (binary) data.
994
995 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
996 method for implementing this specifically for the given file. This function checks which
997 of the method exists, add calls them (with conversion, as needed).
998
999 Args:
1000 abstract_data : dict representing the decoded data
1001 Returns:
1002 binary encoded data
1003 """
Harald Welteb2edd142021-01-08 23:29:35 +01001004 method = getattr(self, '_encode_record_bin', None)
1005 if callable(method):
1006 return method(abstract_data)
1007 method = getattr(self, '_encode_record_hex', None)
1008 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +02001009 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001010 if self._construct:
1011 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +02001012 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001013 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001014 t.from_dict(abstract_data)
1015 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001016 raise NotImplementedError(
1017 "%s encoder not yet implemented. Patches welcome." % self)
1018
Harald Welteb2edd142021-01-08 23:29:35 +01001019
1020class CyclicEF(LinFixedEF):
1021 """Cyclic EF (Entry File) in the smart card filesystem"""
1022 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +01001023
1024 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001025 rec_len={1, None}, **kwargs):
1026 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +01001027
Harald Welteb2edd142021-01-08 23:29:35 +01001028
1029class TransRecEF(TransparentEF):
1030 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +02001031
Harald Welteb2edd142021-01-08 23:29:35 +01001032 These are the real odd-balls and mostly look like mistakes in the specification:
1033 Specified as 'transparent' EF, but actually containing several fixed-length records
1034 inside.
1035 We add a special class for those, so the user only has to provide encoder/decoder functions
1036 for a record, while this class takes care of split / merge of records.
1037 """
Harald Weltec91085e2022-02-10 18:05:45 +01001038
1039 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001040 parent: Optional[CardDF] = None, size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +02001041 """
1042 Args:
1043 fid : File Identifier (4 hex digits)
1044 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +02001045 name : Brief name of the file, like EF_ICCID
1046 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +02001047 parent : Parent CardFile object within filesystem hierarchy
1048 rec_len : Length of the fixed-length records within transparent EF
1049 size : tuple of (minimum_size, recommended_size)
1050 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001051 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +01001052 self.rec_len = rec_len
1053
Harald Weltec91085e2022-02-10 18:05:45 +01001054 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001055 """Decode raw (hex string) data into abstract representation.
1056
1057 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1058 method for implementing this specifically for the given file. This function checks which
1059 of the method exists, add calls them (with conversion, as needed).
1060
1061 Args:
1062 raw_hex_data : hex-encoded data
1063 Returns:
1064 abstract_data; dict representing the decoded data
1065 """
Harald Welteb2edd142021-01-08 23:29:35 +01001066 method = getattr(self, '_decode_record_hex', None)
1067 if callable(method):
1068 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001069 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001070 method = getattr(self, '_decode_record_bin', None)
1071 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001072 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001073 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001074 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001075 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001076 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001077 t.from_tlv(raw_bin_data)
1078 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001079 return {'raw': raw_hex_data}
1080
Harald Weltec91085e2022-02-10 18:05:45 +01001081 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001082 """Decode raw (binary) data into abstract representation.
1083
1084 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1085 method for implementing this specifically for the given file. This function checks which
1086 of the method exists, add calls them (with conversion, as needed).
1087
1088 Args:
1089 raw_bin_data : binary encoded data
1090 Returns:
1091 abstract_data; dict representing the decoded data
1092 """
Harald Welteb2edd142021-01-08 23:29:35 +01001093 method = getattr(self, '_decode_record_bin', None)
1094 if callable(method):
1095 return method(raw_bin_data)
1096 raw_hex_data = b2h(raw_bin_data)
1097 method = getattr(self, '_decode_record_hex', None)
1098 if callable(method):
1099 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001100 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001101 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001102 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001103 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001104 t.from_tlv(raw_bin_data)
1105 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001106 return {'raw': raw_hex_data}
1107
Harald Weltec91085e2022-02-10 18:05:45 +01001108 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001109 """Encode abstract representation into raw (hex string) data.
1110
1111 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1112 method for implementing this specifically for the given file. This function checks which
1113 of the method exists, add calls them (with conversion, as needed).
1114
1115 Args:
1116 abstract_data : dict representing the decoded data
1117 Returns:
1118 hex string encoded data
1119 """
Harald Welteb2edd142021-01-08 23:29:35 +01001120 method = getattr(self, '_encode_record_hex', None)
1121 if callable(method):
1122 return method(abstract_data)
1123 method = getattr(self, '_encode_record_bin', None)
1124 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001125 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001126 if self._construct:
1127 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001128 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001129 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001130 t.from_dict(abstract_data)
1131 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001132 raise NotImplementedError(
1133 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001134
Harald Weltec91085e2022-02-10 18:05:45 +01001135 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001136 """Encode abstract representation into raw (binary) data.
1137
1138 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1139 method for implementing this specifically for the given file. This function checks which
1140 of the method exists, add calls them (with conversion, as needed).
1141
1142 Args:
1143 abstract_data : dict representing the decoded data
1144 Returns:
1145 binary encoded data
1146 """
Harald Welteb2edd142021-01-08 23:29:35 +01001147 method = getattr(self, '_encode_record_bin', None)
1148 if callable(method):
1149 return method(abstract_data)
1150 method = getattr(self, '_encode_record_hex', None)
1151 if callable(method):
1152 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001153 if self._construct:
1154 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001155 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001156 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001157 t.from_dict(abstract_data)
1158 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001159 raise NotImplementedError(
1160 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001161
Harald Weltec91085e2022-02-10 18:05:45 +01001162 def _decode_bin(self, raw_bin_data: bytearray):
1163 chunks = [raw_bin_data[i:i+self.rec_len]
1164 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001165 return [self.decode_record_bin(x) for x in chunks]
1166
Harald Welteee3501f2021-04-02 13:00:18 +02001167 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001168 chunks = [self.encode_record_bin(x) for x in abstract_data]
1169 # FIXME: pad to file size
1170 return b''.join(chunks)
1171
1172
Harald Welte917d98c2021-04-21 11:51:25 +02001173class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001174 """BER-TLV EF (Entry File) in the smart card filesystem.
1175 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001176
Harald Welte27881622021-04-21 11:16:31 +02001177 NOTE: We currently don't really support those, this class is simply a wrapper
1178 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1179 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001180
Harald Welte917d98c2021-04-21 11:51:25 +02001181 @with_default_category('BER-TLV EF Commands')
1182 class ShellCommands(CommandSet):
1183 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001184
Harald Welte917d98c2021-04-21 11:51:25 +02001185 def __init__(self):
1186 super().__init__()
1187
1188 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001189 retrieve_data_parser.add_argument(
1190 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1191
Harald Welte917d98c2021-04-21 11:51:25 +02001192 @cmd2.with_argparser(retrieve_data_parser)
1193 def do_retrieve_data(self, opts):
1194 """Retrieve (Read) data from a BER-TLV EF"""
1195 (data, sw) = self._cmd.rs.retrieve_data(opts.tag)
1196 self._cmd.poutput(data)
1197
1198 def do_retrieve_tags(self, opts):
1199 """List tags available in a given BER-TLV EF"""
1200 tags = self._cmd.rs.retrieve_tags()
1201 self._cmd.poutput(tags)
1202
1203 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001204 set_data_parser.add_argument(
1205 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1206 set_data_parser.add_argument(
1207 'data', help='Data bytes (hex format) to write')
1208
Harald Welte917d98c2021-04-21 11:51:25 +02001209 @cmd2.with_argparser(set_data_parser)
1210 def do_set_data(self, opts):
1211 """Set (Write) data for a given tag in a BER-TLV EF"""
1212 (data, sw) = self._cmd.rs.set_data(opts.tag, opts.data)
1213 if data:
1214 self._cmd.poutput(data)
1215
1216 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001217 del_data_parser.add_argument(
1218 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1219
Harald Welte917d98c2021-04-21 11:51:25 +02001220 @cmd2.with_argparser(del_data_parser)
1221 def do_delete_data(self, opts):
1222 """Delete data for a given tag in a BER-TLV EF"""
1223 (data, sw) = self._cmd.rs.set_data(opts.tag, None)
1224 if data:
1225 self._cmd.poutput(data)
1226
Harald Weltec91085e2022-02-10 18:05:45 +01001227 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001228 size={1, None}, **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001229 """
1230 Args:
1231 fid : File Identifier (4 hex digits)
1232 sfid : Short File Identifier (2 hex digits, optional)
1233 name : Brief name of the file, lik EF_ICCID
1234 desc : Description of the file
1235 parent : Parent CardFile object within filesystem hierarchy
1236 size : tuple of (minimum_size, recommended_size)
1237 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001238 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001239 self._construct = None
1240 self.size = size
1241 self.shell_commands = [self.ShellCommands()]
1242
Harald Welteb2edd142021-01-08 23:29:35 +01001243
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001244class RuntimeState:
Harald Welteb2edd142021-01-08 23:29:35 +01001245 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001246
1247 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001248 """
1249 Args:
1250 card : pysim.cards.Card instance
1251 profile : CardProfile instance
1252 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001253 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001254 self.card = card
Harald Weltec91085e2022-02-10 18:05:45 +01001255 self.selected_file = self.mf # type: CardDF
Harald Welte46a7a3f2022-07-16 11:47:47 +02001256 self.selected_adf = None
Harald Welteb2edd142021-01-08 23:29:35 +01001257 self.profile = profile
Harald Welte2bb17f32022-02-15 15:41:55 +01001258 self.selected_file_fcp = None
1259 self.selected_file_fcp_hex = None
Philipp Maier51cad0d2021-11-08 15:45:10 +01001260
1261 # make sure the class and selection control bytes, which are specified
1262 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001263 self.card.set_apdu_parameter(
1264 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001265
Harald Welte5ce35242021-04-02 20:27:05 +02001266 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001267 apps = self._match_applications()
1268 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001269 if a.adf:
1270 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001271 for f in self.profile.files_in_mf:
1272 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001273 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001274
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001275 # make sure that when the runtime state is created, the card is also
1276 # in a defined state.
1277 self.reset()
1278
Philipp Maier1e896f32021-03-10 17:02:53 +01001279 def _match_applications(self):
1280 """match the applications from the profile with applications on the card"""
1281 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001282
1283 # When the profile does not feature any applications, then we are done already
1284 if not apps_profile:
1285 return []
1286
1287 # Read AIDs from card and match them against the applications defined by the
1288 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001289 aids_card = self.card.read_aids()
1290 apps_taken = []
1291 if aids_card:
1292 aids_taken = []
1293 print("AIDs on card:")
1294 for a in aids_card:
1295 for f in apps_profile:
1296 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001297 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001298 aids_taken.append(a)
1299 apps_taken.append(f)
1300 aids_unknown = set(aids_card) - set(aids_taken)
1301 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001302 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001303 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001304 print("warning: EF.DIR seems to be empty!")
1305
1306 # Some card applications may not be registered in EF.DIR, we will actively
1307 # probe for those applications
1308 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001309 try:
1310 data, sw = self.card.select_adf_by_aid(f.aid)
1311 if sw == "9000":
1312 print(" %s: %s" % (f.name, f.aid))
1313 apps_taken.append(f)
1314 except SwMatchError:
1315 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001316 return apps_taken
1317
Harald Welte747a9782022-02-13 17:52:28 +01001318 def selected_file_descriptor_byte(self) -> dict:
1319 return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
1320
1321 def selected_file_shareable(self) -> bool:
1322 return self.selected_file_descriptor_byte()['shareable']
1323
1324 def selected_file_structure(self) -> str:
1325 return self.selected_file_descriptor_byte()['structure']
1326
1327 def selected_file_type(self) -> str:
1328 return self.selected_file_descriptor_byte()['file_type']
1329
1330 def selected_file_num_of_rec(self) -> Optional[int]:
1331 return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
1332
Harald Weltedaf2b392021-05-03 23:17:29 +02001333 def reset(self, cmd_app=None) -> Hexstr:
1334 """Perform physical card reset and obtain ATR.
1335 Args:
1336 cmd_app : Command Application State (for unregistering old file commands)
1337 """
Philipp Maier946226a2021-10-29 18:31:03 +02001338 atr = i2h(self.card.reset())
Harald Weltedaf2b392021-05-03 23:17:29 +02001339 # select MF to reset internal state and to verify card really works
1340 self.select('MF', cmd_app)
Harald Welte46a7a3f2022-07-16 11:47:47 +02001341 self.selected_adf = None
Harald Weltedaf2b392021-05-03 23:17:29 +02001342 return atr
1343
Harald Welteee3501f2021-04-02 13:00:18 +02001344 def get_cwd(self) -> CardDF:
1345 """Obtain the current working directory.
1346
1347 Returns:
1348 CardDF instance
1349 """
Harald Welteb2edd142021-01-08 23:29:35 +01001350 if isinstance(self.selected_file, CardDF):
1351 return self.selected_file
1352 else:
1353 return self.selected_file.parent
1354
Harald Welte5ce35242021-04-02 20:27:05 +02001355 def get_application_df(self) -> Optional[CardADF]:
1356 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001357
1358 Returns:
1359 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001360 # iterate upwards from selected file; check if any is an ADF
1361 node = self.selected_file
1362 while node.parent != node:
1363 if isinstance(node, CardADF):
1364 return node
1365 node = node.parent
1366 return None
1367
Harald Weltec91085e2022-02-10 18:05:45 +01001368 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001369 """Interpret a given status word relative to the currently selected application
1370 or the underlying card profile.
1371
1372 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001373 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001374
1375 Returns:
1376 Tuple of two strings
1377 """
Harald Welte86fbd392021-04-02 22:13:09 +02001378 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001379 adf = self.get_application_df()
1380 if adf:
1381 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001382 # The application either comes with its own interpret_sw
1383 # method or we will use the interpret_sw method from the
1384 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001385 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001386 res = app.interpret_sw(sw)
1387 return res or self.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001388
Harald Weltec91085e2022-02-10 18:05:45 +01001389 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001390 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001391 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001392 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001393 raise ValueError(
1394 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001395
1396 try:
1397 (data, sw) = self.card._scc.select_file(fid)
1398 except SwMatchError as swm:
1399 k = self.interpret_sw(swm.sw_actual)
1400 if not k:
1401 raise(swm)
1402 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1403
1404 select_resp = self.selected_file.decode_select_response(data)
Harald Welte747a9782022-02-13 17:52:28 +01001405 if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001406 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1407 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001408 else:
Harald Welte747a9782022-02-13 17:52:28 +01001409 if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001410 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1411 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001412 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001413 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1414 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001415
1416 self.selected_file.add_files([f])
1417 self.selected_file = f
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001418 return select_resp, data
Philipp Maier63f572d2021-03-09 22:42:47 +01001419
Harald Welteaceb2a52022-02-12 21:41:59 +01001420 def _select_pre(self, cmd_app):
1421 # unregister commands of old file
1422 if cmd_app and self.selected_file.shell_commands:
1423 for c in self.selected_file.shell_commands:
1424 cmd_app.unregister_command_set(c)
1425
1426 def _select_post(self, cmd_app):
1427 # register commands of new file
1428 if cmd_app and self.selected_file.shell_commands:
1429 for c in self.selected_file.shell_commands:
1430 cmd_app.register_command_set(c)
1431
1432 def select_file(self, file: CardFile, cmd_app=None):
1433 """Select a file (EF, DF, ADF, MF, ...).
1434
1435 Args:
1436 file : CardFile [or derived class] instance
1437 cmd_app : Command Application State (for unregistering old file commands)
1438 """
1439 # we need to find a path from our self.selected_file to the destination
1440 inter_path = self.selected_file.build_select_path_to(file)
1441 if not inter_path:
1442 raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
1443
1444 self._select_pre(cmd_app)
1445
1446 for p in inter_path:
1447 try:
1448 if isinstance(p, CardADF):
1449 (data, sw) = self.card.select_adf_by_aid(p.aid)
Harald Welte46a7a3f2022-07-16 11:47:47 +02001450 self.selected_adf = p
Harald Welteaceb2a52022-02-12 21:41:59 +01001451 else:
1452 (data, sw) = self.card._scc.select_file(p.fid)
1453 self.selected_file = p
1454 except SwMatchError as swm:
1455 self._select_post(cmd_app)
1456 raise(swm)
1457
1458 self._select_post(cmd_app)
1459
Harald Weltec91085e2022-02-10 18:05:45 +01001460 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001461 """Select a file (EF, DF, ADF, MF, ...).
1462
1463 Args:
1464 name : Name of file to select
1465 cmd_app : Command Application State (for unregistering old file commands)
1466 """
Harald Welteee670bc2022-02-13 15:10:15 +01001467 # handling of entire paths with multiple directories/elements
1468 if '/' in name:
1469 prev_sel_file = self.selected_file
1470 pathlist = name.split('/')
1471 # treat /DF.GSM/foo like MF/DF.GSM/foo
1472 if pathlist[0] == '':
1473 pathlist[0] = 'MF'
1474 try:
1475 for p in pathlist:
1476 self.select(p, cmd_app)
1477 return
1478 except Exception as e:
1479 # if any intermediate step fails, go back to where we were
1480 self.select_file(prev_sel_file, cmd_app)
1481 raise e
1482
Harald Welteb2edd142021-01-08 23:29:35 +01001483 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001484 if is_hex(name):
1485 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001486
Harald Welteaceb2a52022-02-12 21:41:59 +01001487 self._select_pre(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001488
Harald Welteb2edd142021-01-08 23:29:35 +01001489 if name in sels:
1490 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001491 try:
1492 if isinstance(f, CardADF):
Philipp Maiercba6dbc2021-03-11 13:03:18 +01001493 (data, sw) = self.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001494 else:
1495 (data, sw) = self.card._scc.select_file(f.fid)
1496 self.selected_file = f
1497 except SwMatchError as swm:
1498 k = self.interpret_sw(swm.sw_actual)
1499 if not k:
1500 raise(swm)
1501 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001502 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001503 else:
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001504 (select_resp, data) = self.probe_file(name, cmd_app)
1505
Harald Welte2bb17f32022-02-15 15:41:55 +01001506 # store the raw + decoded FCP for later reference
1507 self.selected_file_fcp_hex = data
Harald Welte850b72a2021-04-07 09:33:03 +02001508 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001509
Harald Welteaceb2a52022-02-12 21:41:59 +01001510 self._select_post(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001511 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001512
Harald Welte34b05d32021-05-25 22:03:13 +02001513 def status(self):
1514 """Request STATUS (current selected file FCP) from card."""
1515 (data, sw) = self.card._scc.status()
1516 return self.selected_file.decode_select_response(data)
1517
Harald Welte3c9b7842021-10-19 21:44:24 +02001518 def get_file_for_selectable(self, name: str):
1519 sels = self.selected_file.get_selectables()
1520 return sels[name]
1521
Harald Weltec91085e2022-02-10 18:05:45 +01001522 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001523 """Request ACTIVATE FILE of specified file."""
1524 sels = self.selected_file.get_selectables()
1525 f = sels[name]
1526 data, sw = self.card._scc.activate_file(f.fid)
1527 return data, sw
1528
Harald Weltec91085e2022-02-10 18:05:45 +01001529 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001530 """Read [part of] a transparent EF binary data.
1531
1532 Args:
1533 length : Amount of data to read (None: as much as possible)
1534 offset : Offset into the file from which to read 'length' bytes
1535 Returns:
1536 binary data read from the file
1537 """
Harald Welteb2edd142021-01-08 23:29:35 +01001538 if not isinstance(self.selected_file, TransparentEF):
1539 raise TypeError("Only works with TransparentEF")
1540 return self.card._scc.read_binary(self.selected_file.fid, length, offset)
1541
Harald Welte2d4a64b2021-04-03 09:01:24 +02001542 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001543 """Read [part of] a transparent EF binary data and decode it.
1544
1545 Args:
1546 length : Amount of data to read (None: as much as possible)
1547 offset : Offset into the file from which to read 'length' bytes
1548 Returns:
1549 abstract decode data read from the file
1550 """
Harald Welteb2edd142021-01-08 23:29:35 +01001551 (data, sw) = self.read_binary()
1552 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001553 return (dec_data, sw)
1554
Harald Weltec91085e2022-02-10 18:05:45 +01001555 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001556 """Update transparent EF binary data.
1557
1558 Args:
1559 data_hex : hex string of data to be written
1560 offset : Offset into the file from which to write 'data_hex'
1561 """
Harald Welteb2edd142021-01-08 23:29:35 +01001562 if not isinstance(self.selected_file, TransparentEF):
1563 raise TypeError("Only works with TransparentEF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001564 return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001565
Harald Weltec91085e2022-02-10 18:05:45 +01001566 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001567 """Update transparent EF from abstract data. Encodes the data to binary and
1568 then updates the EF with it.
1569
1570 Args:
1571 data : abstract data which is to be encoded and written
1572 """
Harald Welteb2edd142021-01-08 23:29:35 +01001573 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001574 return self.update_binary(data_hex)
1575
Harald Weltec91085e2022-02-10 18:05:45 +01001576 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001577 """Read a record as binary data.
1578
1579 Args:
1580 rec_nr : Record number to read
1581 Returns:
1582 hex string of binary data contained in record
1583 """
Harald Welteb2edd142021-01-08 23:29:35 +01001584 if not isinstance(self.selected_file, LinFixedEF):
1585 raise TypeError("Only works with Linear Fixed EF")
1586 # returns a string of hex nibbles
1587 return self.card._scc.read_record(self.selected_file.fid, rec_nr)
1588
Harald Weltec91085e2022-02-10 18:05:45 +01001589 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001590 """Read a record and decode it to abstract data.
1591
1592 Args:
1593 rec_nr : Record number to read
1594 Returns:
1595 abstract data contained in record
1596 """
Harald Welteb2edd142021-01-08 23:29:35 +01001597 (data, sw) = self.read_record(rec_nr)
1598 return (self.selected_file.decode_record_hex(data), sw)
1599
Harald Weltec91085e2022-02-10 18:05:45 +01001600 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001601 """Update a record with given binary data
1602
1603 Args:
1604 rec_nr : Record number to read
1605 data_hex : Hex string binary data to be written
1606 """
Harald Welteb2edd142021-01-08 23:29:35 +01001607 if not isinstance(self.selected_file, LinFixedEF):
1608 raise TypeError("Only works with Linear Fixed EF")
Philipp Maier38c74f62021-03-17 17:19:52 +01001609 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 +01001610
Harald Weltec91085e2022-02-10 18:05:45 +01001611 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001612 """Update a record with given abstract data. Will encode abstract to binary data
1613 and then write it to the given record on the card.
1614
1615 Args:
1616 rec_nr : Record number to read
1617 data_hex : Abstract data to be written
1618 """
Harald Welte1e456572021-04-02 17:16:30 +02001619 data_hex = self.selected_file.encode_record_hex(data)
1620 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001621
Harald Weltec91085e2022-02-10 18:05:45 +01001622 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001623 """Read a DO/TLV as binary data.
1624
1625 Args:
1626 tag : Tag of TLV/DO to read
1627 Returns:
1628 hex string of full BER-TLV DO including Tag and Length
1629 """
1630 if not isinstance(self.selected_file, BerTlvEF):
1631 raise TypeError("Only works with BER-TLV EF")
1632 # returns a string of hex nibbles
1633 return self.card._scc.retrieve_data(self.selected_file.fid, tag)
1634
1635 def retrieve_tags(self):
1636 """Retrieve tags available on BER-TLV EF.
1637
1638 Returns:
1639 list of integer tags contained in EF
1640 """
1641 if not isinstance(self.selected_file, BerTlvEF):
1642 raise TypeError("Only works with BER-TLV EF")
1643 data, sw = self.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001644 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001645 return list(value)
1646
Harald Weltec91085e2022-02-10 18:05:45 +01001647 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001648 """Update a TLV/DO with given binary data
1649
1650 Args:
1651 tag : Tag of TLV/DO to be written
1652 data_hex : Hex string binary data to be written (value portion)
1653 """
1654 if not isinstance(self.selected_file, BerTlvEF):
1655 raise TypeError("Only works with BER-TLV EF")
1656 return self.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.conserve_write)
1657
Philipp Maier5d698e52021-09-16 13:18:01 +02001658 def unregister_cmds(self, cmd_app=None):
1659 """Unregister all file specific commands."""
1660 if cmd_app and self.selected_file.shell_commands:
1661 for c in self.selected_file.shell_commands:
1662 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001663
Harald Welteb2edd142021-01-08 23:29:35 +01001664
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001665class FileData:
Harald Welteb2edd142021-01-08 23:29:35 +01001666 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001667
Harald Welteb2edd142021-01-08 23:29:35 +01001668 def __init__(self, fdesc):
1669 self.desc = fdesc
1670 self.fcp = None
1671
1672
Harald Weltec91085e2022-02-10 18:05:45 +01001673def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001674 """Interpret a given status word.
1675
1676 Args:
1677 sw_data : Hierarchical dict of status word matches
1678 sw : status word to match (string of 4 hex digits)
1679 Returns:
1680 tuple of two strings (class_string, description)
1681 """
Harald Welteb2edd142021-01-08 23:29:35 +01001682 for class_str, swdict in sw_data.items():
1683 # first try direct match
1684 if sw in swdict:
1685 return (class_str, swdict[sw])
1686 # next try wildcard matches
1687 for pattern, descr in swdict.items():
1688 if sw_match(sw, pattern):
1689 return (class_str, descr)
1690 return None
1691
Harald Weltec91085e2022-02-10 18:05:45 +01001692
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001693class CardApplication:
Harald Welteb2edd142021-01-08 23:29:35 +01001694 """A card application is represented by an ADF (with contained hierarchy) and optionally
1695 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001696
1697 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001698 """
1699 Args:
1700 adf : ADF name
1701 sw : Dict of status word conversions
1702 """
Harald Welteb2edd142021-01-08 23:29:35 +01001703 self.name = name
1704 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001705 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001706 # back-reference from ADF to Applicaiton
1707 if self.adf:
1708 self.aid = aid or self.adf.aid
1709 self.adf.application = self
1710 else:
1711 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001712
1713 def __str__(self):
1714 return "APP(%s)" % (self.name)
1715
1716 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001717 """Interpret a given status word within the application.
1718
1719 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001720 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001721
1722 Returns:
1723 Tuple of two strings
1724 """
Harald Welteb2edd142021-01-08 23:29:35 +01001725 return interpret_sw(self.sw, sw)
1726
Harald Weltef44256c2021-10-14 15:53:39 +02001727
1728class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001729 """A specific card model, typically having some additional vendor-specific files. All
1730 you need to do is to define a sub-class with a list of ATRs or an overridden match
1731 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001732 _atrs = []
1733
1734 @classmethod
1735 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001736 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001737 """Add model specific files to given RuntimeState."""
1738
1739 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001740 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001741 """Test if given card matches this model."""
1742 card_atr = scc.get_atr()
1743 for atr in cls._atrs:
1744 atr_bin = toBytes(atr)
1745 if atr_bin == card_atr:
1746 print("Detected CardModel:", cls.__name__)
1747 return True
1748 return False
1749
1750 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001751 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001752 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1753 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1754 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001755 for m in CardModel.__subclasses__():
1756 if m.match(scc):
1757 m.add_files(rs)