blob: 5e697bfea196d549bd842f34ae1173faeb0b050a [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001# coding=utf-8
2"""Representation of the ISO7816-4 filesystem model.
3
4The File (and its derived classes) represent the structure / hierarchy
5of the ISO7816-4 smart card file system with the MF, DF, EF and ADF
6entries, further sub-divided into the EF sub-types Transparent, Linear Fixed, etc.
7
8The classes are intended to represent the *specification* of the filesystem,
9not the actual contents / runtime state of interacting with a given smart card.
Harald Welteb2edd142021-01-08 23:29:35 +010010"""
11
Harald Welte5a4fd522021-04-02 16:05:26 +020012# (C) 2021 by Harald Welte <laforge@osmocom.org>
13#
14# This program is free software: you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation, either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program. If not, see <http://www.gnu.org/licenses/>.
26
Harald Welteb2edd142021-01-08 23:29:35 +010027import code
Harald Welte4145d3c2021-04-08 20:34:13 +020028import tempfile
Harald Welteb2edd142021-01-08 23:29:35 +010029import json
Harald Weltef44256c2021-10-14 15:53:39 +020030import abc
31import inspect
Harald Welteb2edd142021-01-08 23:29:35 +010032
33import cmd2
34from cmd2 import CommandSet, with_default_category, with_argparser
35import argparse
36
Harald Welte9170fbf2022-02-11 21:54:37 +010037from typing import cast, Optional, Iterable, List, Dict, Tuple, Union
Harald Welteee3501f2021-04-02 13:00:18 +020038
Harald Weltef44256c2021-10-14 15:53:39 +020039from smartcard.util import toBytes
40
Harald Weltedaf2b392021-05-03 23:17:29 +020041from pySim.utils import sw_match, h2b, b2h, i2h, is_hex, auto_int, bertlv_parse_one, Hexstr
Harald Welte07c7b1f2021-05-28 22:01:29 +020042from pySim.construct import filter_dict, parse_construct
Harald Welteb2edd142021-01-08 23:29:35 +010043from pySim.exceptions import *
Harald Welte0d4e98a2021-04-07 00:14:40 +020044from pySim.jsonpath import js_path_find, js_path_modify
Harald Weltef44256c2021-10-14 15:53:39 +020045from pySim.commands import SimCardCommands
Harald Welteb2edd142021-01-08 23:29:35 +010046
Harald Welte9170fbf2022-02-11 21:54:37 +010047# int: a single service is associated with this file
48# list: any of the listed services requires this file
49# tuple: logical-and of the listed services requires this file
50CardFileService = Union[int, List[int], Tuple[int, ...]]
Harald Weltec91085e2022-02-10 18:05:45 +010051
Harald Weltea6c0f882022-07-17 14:23:17 +020052def lchan_nr_from_cla(cla: int) -> int:
53 """Resolve the logical channel number from the CLA byte."""
54 # TS 102 221 10.1.1 Coding of Class Byte
55 if cla >> 4 in [0x0, 0xA, 0x8]:
56 # Table 10.3
57 return cla & 0x03
58 elif cla & 0xD0 in [0x40, 0xC0]:
59 # Table 10.4a
60 return 4 + (cla & 0x0F)
61 else:
62 raise ValueError('Could not determine logical channel for CLA=%2X' % cla)
63
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +070064class CardFile:
Harald Welteb2edd142021-01-08 23:29:35 +010065 """Base class for all objects in the smart card filesystem.
66 Serve as a common ancestor to all other file types; rarely used directly.
67 """
68 RESERVED_NAMES = ['..', '.', '/', 'MF']
69 RESERVED_FIDS = ['3f00']
70
Harald Weltec91085e2022-02-10 18:05:45 +010071 def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +010072 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None,
73 service: Optional[CardFileService] = None):
Harald Welteee3501f2021-04-02 13:00:18 +020074 """
75 Args:
76 fid : File Identifier (4 hex digits)
77 sfid : Short File Identifier (2 hex digits, optional)
78 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020079 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020080 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier5af7bdf2021-11-04 12:48:41 +010081 profile : Card profile that this file should be part of
Harald Welte9170fbf2022-02-11 21:54:37 +010082 service : Service (SST/UST/IST) associated with the file
Harald Welteee3501f2021-04-02 13:00:18 +020083 """
Harald Welteb2edd142021-01-08 23:29:35 +010084 if not isinstance(self, CardADF) and fid == None:
85 raise ValueError("fid is mandatory")
86 if fid:
87 fid = fid.lower()
88 self.fid = fid # file identifier
89 self.sfid = sfid # short file identifier
90 self.name = name # human readable name
91 self.desc = desc # human readable description
92 self.parent = parent
93 if self.parent and self.parent != self and self.fid:
94 self.parent.add_file(self)
Philipp Maier5af7bdf2021-11-04 12:48:41 +010095 self.profile = profile
Harald Welte9170fbf2022-02-11 21:54:37 +010096 self.service = service
Harald Weltec91085e2022-02-10 18:05:45 +010097 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +010098
Harald Weltec91085e2022-02-10 18:05:45 +010099 # Note: the basic properties (fid, name, ect.) are verified when
100 # the file is attached to a parent file. See method add_file() in
101 # class Card DF
Philipp Maier66061582021-03-09 21:57:57 +0100102
Harald Welteb2edd142021-01-08 23:29:35 +0100103 def __str__(self):
104 if self.name:
105 return self.name
106 else:
107 return self.fid
108
Harald Weltec91085e2022-02-10 18:05:45 +0100109 def _path_element(self, prefer_name: bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +0100110 if prefer_name and self.name:
111 return self.name
112 else:
113 return self.fid
114
Harald Weltec91085e2022-02-10 18:05:45 +0100115 def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200116 """Return fully qualified path to file as list of FID or name strings.
117
118 Args:
119 prefer_name : Preferably build path of names; fall-back to FIDs as required
120 """
Harald Welte1e456572021-04-02 17:16:30 +0200121 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +0100122 ret = self.parent.fully_qualified_path(prefer_name)
123 else:
124 ret = []
Harald Welte1e456572021-04-02 17:16:30 +0200125 elem = self._path_element(prefer_name)
126 if elem:
127 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100128 return ret
129
Harald Welteaceb2a52022-02-12 21:41:59 +0100130 def fully_qualified_path_fobj(self) -> List['CardFile']:
131 """Return fully qualified path to file as list of CardFile instance references."""
132 if self.parent and self.parent != self:
133 ret = self.parent.fully_qualified_path_fobj()
134 else:
135 ret = []
136 if self:
137 ret.append(self)
138 return ret
139
140 def build_select_path_to(self, target: 'CardFile') -> Optional[List['CardFile']]:
141 """Build the relative sequence of files we need to traverse to get from us to 'target'."""
142 cur_fqpath = self.fully_qualified_path_fobj()
143 target_fqpath = target.fully_qualified_path_fobj()
144 inter_path = []
145 cur_fqpath.pop() # drop last element (currently selected file, doesn't need re-selection
146 cur_fqpath.reverse()
147 for ce in cur_fqpath:
148 inter_path.append(ce)
149 for i in range(0, len(target_fqpath)-1):
150 te = target_fqpath[i]
151 if te == ce:
152 for te2 in target_fqpath[i+1:]:
153 inter_path.append(te2)
154 # we found our common ancestor
155 return inter_path
156 return None
157
Harald Welteee3501f2021-04-02 13:00:18 +0200158 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100159 """Return the MF (root) of the file system."""
160 if self.parent == None:
161 return None
162 # iterate towards the top. MF has parent == self
163 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200164 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100165 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200166 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100167
Harald Weltec91085e2022-02-10 18:05:45 +0100168 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200169 """Return a dict of {'identifier': self} tuples.
170
171 Args:
172 alias : Add an alias with given name to 'self'
173 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
174 If not specified, all selectables will be returned.
175 Returns:
176 dict containing reference to 'self' for all identifiers.
177 """
Harald Welteb2edd142021-01-08 23:29:35 +0100178 sels = {}
179 if alias:
180 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100181 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100182 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100183 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100184 sels.update({self.name: self})
185 return sels
186
Harald Weltec91085e2022-02-10 18:05:45 +0100187 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200188 """Return a dict of {'identifier': File} that is selectable from the current file.
189
190 Args:
191 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
192 If not specified, all selectables will be returned.
193 Returns:
194 dict containing all selectable items. Key is identifier (string), value
195 a reference to a CardFile (or derived class) instance.
196 """
Philipp Maier786f7812021-02-25 16:48:10 +0100197 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100198 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100199 if flags == [] or 'SELF' in flags:
200 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100201 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100202 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200203 if self.parent:
204 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100205 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100206 if flags == [] or 'MF' in flags:
207 mf = self.get_mf()
208 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100209 sels.update(mf._get_self_selectables(flags=flags))
210 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100211 return sels
212
Harald Weltec91085e2022-02-10 18:05:45 +0100213 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200214 """Return a dict of {'identifier': File} that is selectable from the current file.
215
216 Args:
217 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
218 If not specified, all selectables will be returned.
219 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200220 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200221 """
Philipp Maier786f7812021-02-25 16:48:10 +0100222 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100223 sel_keys = list(sels.keys())
224 sel_keys.sort()
225 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100226
Harald Weltec91085e2022-02-10 18:05:45 +0100227 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100228 """Decode the response to a SELECT command.
229
230 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100231 data_hex: Hex string of the select response
232 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100233
Harald Weltec91085e2022-02-10 18:05:45 +0100234 # When the current file does not implement a custom select response decoder,
235 # we just ask the parent file to decode the select response. If this method
236 # is not overloaded by the current file we will again ask the parent file.
237 # This way we recursively travel up the file system tree until we hit a file
238 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200239 if self.parent:
240 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100241
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100242 def get_profile(self):
243 """Get the profile associated with this file. If this file does not have any
244 profile assigned, try to find a file above (usually the MF) in the filesystem
245 hirarchy that has a profile assigned
246 """
247
248 # If we have a profile set, return it
249 if self.profile:
250 return self.profile
251
252 # Walk up recursively until we hit a parent that has a profile set
253 if self.parent:
254 return self.parent.get_profile()
255 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100256
Harald Welte9170fbf2022-02-11 21:54:37 +0100257 def should_exist_for_services(self, services: List[int]):
258 """Assuming the provided list of activated services, should this file exist and be activated?."""
259 if self.service is None:
260 return None
261 elif isinstance(self.service, int):
262 # a single service determines the result
263 return self.service in services
264 elif isinstance(self.service, list):
265 # any of the services active -> true
266 for s in self.service:
267 if s in services:
268 return True
269 return False
270 elif isinstance(self.service, tuple):
271 # all of the services active -> true
272 for s in self.service:
273 if not s in services:
274 return False
275 return True
276 else:
277 raise ValueError("self.service must be either int or list or tuple")
278
Harald Weltec91085e2022-02-10 18:05:45 +0100279
Harald Welteb2edd142021-01-08 23:29:35 +0100280class CardDF(CardFile):
281 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100282
283 @with_default_category('DF/ADF Commands')
284 class ShellCommands(CommandSet):
285 def __init__(self):
286 super().__init__()
287
Harald Welteb2edd142021-01-08 23:29:35 +0100288 def __init__(self, **kwargs):
289 if not isinstance(self, CardADF):
290 if not 'fid' in kwargs:
291 raise TypeError('fid is mandatory for all DF')
292 super().__init__(**kwargs)
293 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100294 self.shell_commands = [self.ShellCommands()]
Harald Welte9170fbf2022-02-11 21:54:37 +0100295 # dict of CardFile affected by service(int), indexed by service
296 self.files_by_service = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100297
298 def __str__(self):
299 return "DF(%s)" % (super().__str__())
300
Harald Welte9170fbf2022-02-11 21:54:37 +0100301 def _add_file_services(self, child):
302 """Add a child (DF/EF) to the files_by_services of the parent."""
303 if not child.service:
304 return
305 if isinstance(child.service, int):
306 self.files_by_service.setdefault(child.service, []).append(child)
307 elif isinstance(child.service, list):
308 for service in child.service:
309 self.files_by_service.setdefault(service, []).append(child)
310 elif isinstance(child.service, tuple):
311 for service in child.service:
312 self.files_by_service.setdefault(service, []).append(child)
313 else:
314 raise ValueError
315
Harald Welted56f45d2022-07-16 11:46:59 +0200316 def _has_service(self):
317 if self.service:
318 return True
319 for c in self.children.values():
320 if isinstance(c, CardDF):
321 if c._has_service():
322 return True
323
Harald Weltec91085e2022-02-10 18:05:45 +0100324 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200325 """Add a child (DF/EF) to this DF.
326 Args:
327 child: The new DF/EF to be added
328 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
329 """
Harald Welteb2edd142021-01-08 23:29:35 +0100330 if not isinstance(child, CardFile):
331 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100332 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100333 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100334 if child.name in CardFile.RESERVED_NAMES:
335 raise ValueError("File name %s is a reserved name" % (child.name))
336 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100337 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100338 if child.fid in self.children:
339 if ignore_existing:
340 return
Harald Weltec91085e2022-02-10 18:05:45 +0100341 raise ValueError(
342 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100343 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100344 raise ValueError(
345 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100346 if self.lookup_file_by_name(child.name):
347 if ignore_existing:
348 return
Harald Weltec91085e2022-02-10 18:05:45 +0100349 raise ValueError(
350 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100351 self.children[child.fid] = child
352 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100353 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100354 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100355 if isinstance(child, CardDF):
356 for c in child.children.values():
357 self._add_file_services(c)
358 if isinstance(c, CardDF):
Harald Welted56f45d2022-07-16 11:46:59 +0200359 for gc in c.children.values():
360 if isinstance(gc, CardDF):
361 if gc._has_service():
362 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100363
Harald Weltec91085e2022-02-10 18:05:45 +0100364 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200365 """Add a list of child (DF/EF) to this DF
366
367 Args:
368 children: List of new DF/EFs to be added
369 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
370 """
Harald Welteb2edd142021-01-08 23:29:35 +0100371 for child in children:
372 self.add_file(child, ignore_existing)
373
Harald Weltec91085e2022-02-10 18:05:45 +0100374 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200375 """Return a dict of {'identifier': File} that is selectable from the current DF.
376
377 Args:
378 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
379 If not specified, all selectables will be returned.
380 Returns:
381 dict containing all selectable items. Key is identifier (string), value
382 a reference to a CardFile (or derived class) instance.
383 """
Harald Welteb2edd142021-01-08 23:29:35 +0100384 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100385 sels = super().get_selectables(flags)
386 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100387 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100388 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100389 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100390 return sels
391
Harald Weltec91085e2022-02-10 18:05:45 +0100392 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200393 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100394 if name == None:
395 return None
396 for i in self.children.values():
397 if i.name and i.name == name:
398 return i
399 return None
400
Harald Weltec91085e2022-02-10 18:05:45 +0100401 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200402 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100403 if sfid == None:
404 return None
405 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200406 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100407 return i
408 return None
409
Harald Weltec91085e2022-02-10 18:05:45 +0100410 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200411 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100412 if fid in self.children:
413 return self.children[fid]
414 return None
415
416
417class CardMF(CardDF):
418 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100419
Harald Welteb2edd142021-01-08 23:29:35 +0100420 def __init__(self, **kwargs):
421 # can be overridden; use setdefault
422 kwargs.setdefault('fid', '3f00')
423 kwargs.setdefault('name', 'MF')
424 kwargs.setdefault('desc', 'Master File (directory root)')
425 # cannot be overridden; use assignment
426 kwargs['parent'] = self
427 super().__init__(**kwargs)
428 self.applications = dict()
429
430 def __str__(self):
431 return "MF(%s)" % (self.fid)
432
Harald Weltec91085e2022-02-10 18:05:45 +0100433 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200434 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100435 if not isinstance(app, CardADF):
436 raise TypeError("Expected an ADF instance")
437 if app.aid in self.applications:
438 raise ValueError("AID %s already exists" % (app.aid))
439 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100440 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100441
442 def get_app_names(self):
443 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100444 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100445
Harald Weltec91085e2022-02-10 18:05:45 +0100446 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200447 """Return a dict of {'identifier': File} that is selectable from the current DF.
448
449 Args:
450 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
451 If not specified, all selectables will be returned.
452 Returns:
453 dict containing all selectable items. Key is identifier (string), value
454 a reference to a CardFile (or derived class) instance.
455 """
Philipp Maier786f7812021-02-25 16:48:10 +0100456 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100457 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100458 return sels
459
Harald Weltec91085e2022-02-10 18:05:45 +0100460 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100461 """Get applications by AID + name"""
462 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100463 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100464 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100465 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100466 sels.update(
467 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100468 return sels
469
Harald Weltec9752512022-02-11 16:31:15 +0100470 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200471 """Decode the response to a SELECT command.
472
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100473 This is the fall-back method which automatically defers to the standard decoding
474 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100475 performed. Specific derived classes (usually ADF) can overload this method to
476 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200477 """
Harald Welteb2edd142021-01-08 23:29:35 +0100478
Harald Weltec9752512022-02-11 16:31:15 +0100479 if not data_hex:
480 return data_hex
481
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100482 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100483
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100484 if profile:
485 return profile.decode_select_response(data_hex)
486 else:
487 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100488
Harald Weltec91085e2022-02-10 18:05:45 +0100489
Harald Welteb2edd142021-01-08 23:29:35 +0100490class CardADF(CardDF):
491 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100492
493 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100494 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200495 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200496 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100497 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200498 mf = self.get_mf()
499 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200500 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100501
502 def __str__(self):
Harald Welte4b003652022-07-16 11:55:07 +0200503 return "ADF(%s)" % (self.name if self.name else self.aid)
Harald Welteb2edd142021-01-08 23:29:35 +0100504
Harald Weltec91085e2022-02-10 18:05:45 +0100505 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100506 if self.name and prefer_name:
507 return self.name
508 else:
509 return self.aid
510
511
512class CardEF(CardFile):
513 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100514
Harald Welteb2edd142021-01-08 23:29:35 +0100515 def __init__(self, *, fid, **kwargs):
516 kwargs['fid'] = fid
517 super().__init__(**kwargs)
518
519 def __str__(self):
520 return "EF(%s)" % (super().__str__())
521
Harald Weltec91085e2022-02-10 18:05:45 +0100522 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200523 """Return a dict of {'identifier': File} that is selectable from the current DF.
524
525 Args:
526 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
527 If not specified, all selectables will be returned.
528 Returns:
529 dict containing all selectable items. Key is identifier (string), value
530 a reference to a CardFile (or derived class) instance.
531 """
Harald Weltec91085e2022-02-10 18:05:45 +0100532 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100533 sels = super().get_selectables(flags)
Harald Weltec91085e2022-02-10 18:05:45 +0100534 sels.update(
535 {x.name: x for x in self.parent.children.values() if x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100536 return sels
537
538
539class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200540 """Transparent EF (Entry File) in the smart card filesystem.
541
542 A Transparent EF is a binary file with no formal structure. This is contrary to
543 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100544
545 @with_default_category('Transparent EF Commands')
546 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200547 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100548
Harald Welteb2edd142021-01-08 23:29:35 +0100549 def __init__(self):
550 super().__init__()
551
Harald Welteaefd0642022-02-25 15:26:37 +0100552 dec_hex_parser = argparse.ArgumentParser()
553 dec_hex_parser.add_argument('--oneline', action='store_true',
554 help='No JSON pretty-printing, dump as a single line')
555 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
556
557 @cmd2.with_argparser(dec_hex_parser)
558 def do_decode_hex(self, opts):
559 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200560 data = self._cmd.lchan.selected_file.decode_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100561 self._cmd.poutput_json(data, opts.oneline)
562
Harald Welteb2edd142021-01-08 23:29:35 +0100563 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100564 read_bin_parser.add_argument(
565 '--offset', type=int, default=0, help='Byte offset for start of read')
566 read_bin_parser.add_argument(
567 '--length', type=int, help='Number of bytes to read')
568
Harald Welteb2edd142021-01-08 23:29:35 +0100569 @cmd2.with_argparser(read_bin_parser)
570 def do_read_binary(self, opts):
571 """Read binary data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200572 (data, sw) = self._cmd.lchan.read_binary(opts.length, opts.offset)
Harald Welteb2edd142021-01-08 23:29:35 +0100573 self._cmd.poutput(data)
574
Harald Weltebcad86c2021-04-06 20:08:39 +0200575 read_bin_dec_parser = argparse.ArgumentParser()
576 read_bin_dec_parser.add_argument('--oneline', action='store_true',
577 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100578
Harald Weltebcad86c2021-04-06 20:08:39 +0200579 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100580 def do_read_binary_decoded(self, opts):
581 """Read + decode data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200582 (data, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200583 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100584
585 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100586 upd_bin_parser.add_argument(
587 '--offset', type=int, default=0, help='Byte offset for start of read')
588 upd_bin_parser.add_argument(
589 'data', help='Data bytes (hex format) to write')
590
Harald Welteb2edd142021-01-08 23:29:35 +0100591 @cmd2.with_argparser(upd_bin_parser)
592 def do_update_binary(self, opts):
593 """Update (Write) data of a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200594 (data, sw) = self._cmd.lchan.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100595 if data:
596 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100597
598 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100599 upd_bin_dec_parser.add_argument(
600 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200601 upd_bin_dec_parser.add_argument('--json-path', type=str,
602 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100603
Harald Welteb2edd142021-01-08 23:29:35 +0100604 @cmd2.with_argparser(upd_bin_dec_parser)
605 def do_update_binary_decoded(self, opts):
606 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200607 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200608 (data_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100609 js_path_modify(data_json, opts.json_path,
610 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200611 else:
612 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200613 (data, sw) = self._cmd.lchan.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100614 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200615 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100616
Harald Welte4145d3c2021-04-08 20:34:13 +0200617 def do_edit_binary_decoded(self, opts):
618 """Edit the JSON representation of the EF contents in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200619 (orig_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte4145d3c2021-04-08 20:34:13 +0200620 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
621 filename = '%s/file' % dirname
622 # write existing data as JSON to file
623 with open(filename, 'w') as text_file:
624 json.dump(orig_json, text_file, indent=4)
625 # run a text editor
626 self._cmd._run_editor(filename)
627 with open(filename, 'r') as text_file:
628 edited_json = json.load(text_file)
629 if edited_json == orig_json:
630 self._cmd.poutput("Data not modified, skipping write")
631 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200632 (data, sw) = self._cmd.lchan.update_binary_dec(edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200633 if data:
634 self._cmd.poutput_json(data)
635
Harald Weltec91085e2022-02-10 18:05:45 +0100636 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100637 size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200638 """
639 Args:
640 fid : File Identifier (4 hex digits)
641 sfid : Short File Identifier (2 hex digits, optional)
642 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200643 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200644 parent : Parent CardFile object within filesystem hierarchy
645 size : tuple of (minimum_size, recommended_size)
646 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100647 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200648 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200649 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100650 self.size = size
651 self.shell_commands = [self.ShellCommands()]
652
Harald Weltec91085e2022-02-10 18:05:45 +0100653 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200654 """Decode raw (binary) data into abstract representation.
655
656 A derived class would typically provide a _decode_bin() or _decode_hex() method
657 for implementing this specifically for the given file. This function checks which
658 of the method exists, add calls them (with conversion, as needed).
659
660 Args:
661 raw_bin_data : binary encoded data
662 Returns:
663 abstract_data; dict representing the decoded data
664 """
Harald Welteb2edd142021-01-08 23:29:35 +0100665 method = getattr(self, '_decode_bin', None)
666 if callable(method):
667 return method(raw_bin_data)
668 method = getattr(self, '_decode_hex', None)
669 if callable(method):
670 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200671 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200672 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200673 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100674 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100675 t.from_tlv(raw_bin_data)
676 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100677 return {'raw': raw_bin_data.hex()}
678
Harald Weltec91085e2022-02-10 18:05:45 +0100679 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200680 """Decode raw (hex string) data into abstract representation.
681
682 A derived class would typically provide a _decode_bin() or _decode_hex() method
683 for implementing this specifically for the given file. This function checks which
684 of the method exists, add calls them (with conversion, as needed).
685
686 Args:
687 raw_hex_data : hex-encoded data
688 Returns:
689 abstract_data; dict representing the decoded data
690 """
Harald Welteb2edd142021-01-08 23:29:35 +0100691 method = getattr(self, '_decode_hex', None)
692 if callable(method):
693 return method(raw_hex_data)
694 raw_bin_data = h2b(raw_hex_data)
695 method = getattr(self, '_decode_bin', None)
696 if callable(method):
697 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200698 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200699 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200700 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100701 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100702 t.from_tlv(raw_bin_data)
703 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100704 return {'raw': raw_bin_data.hex()}
705
Harald Weltec91085e2022-02-10 18:05:45 +0100706 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200707 """Encode abstract representation into raw (binary) data.
708
709 A derived class would typically provide an _encode_bin() or _encode_hex() method
710 for implementing this specifically for the given file. This function checks which
711 of the method exists, add calls them (with conversion, as needed).
712
713 Args:
714 abstract_data : dict representing the decoded data
715 Returns:
716 binary encoded data
717 """
Harald Welteb2edd142021-01-08 23:29:35 +0100718 method = getattr(self, '_encode_bin', None)
719 if callable(method):
720 return method(abstract_data)
721 method = getattr(self, '_encode_hex', None)
722 if callable(method):
723 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200724 if self._construct:
725 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200726 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100727 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100728 t.from_dict(abstract_data)
729 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100730 raise NotImplementedError(
731 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100732
Harald Weltec91085e2022-02-10 18:05:45 +0100733 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200734 """Encode abstract representation into raw (hex string) data.
735
736 A derived class would typically provide an _encode_bin() or _encode_hex() method
737 for implementing this specifically for the given file. This function checks which
738 of the method exists, add calls them (with conversion, as needed).
739
740 Args:
741 abstract_data : dict representing the decoded data
742 Returns:
743 hex string encoded data
744 """
Harald Welteb2edd142021-01-08 23:29:35 +0100745 method = getattr(self, '_encode_hex', None)
746 if callable(method):
747 return method(abstract_data)
748 method = getattr(self, '_encode_bin', None)
749 if callable(method):
750 raw_bin_data = method(abstract_data)
751 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200752 if self._construct:
753 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200754 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100755 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100756 t.from_dict(abstract_data)
757 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100758 raise NotImplementedError(
759 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100760
761
762class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200763 """Linear Fixed EF (Entry File) in the smart card filesystem.
764
765 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
766 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100767
768 @with_default_category('Linear Fixed EF Commands')
769 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200770 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100771
Harald Welte9170fbf2022-02-11 21:54:37 +0100772 def __init__(self, **kwargs):
773 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100774
Harald Welteaefd0642022-02-25 15:26:37 +0100775 dec_hex_parser = argparse.ArgumentParser()
776 dec_hex_parser.add_argument('--oneline', action='store_true',
777 help='No JSON pretty-printing, dump as a single line')
778 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
779
780 @cmd2.with_argparser(dec_hex_parser)
781 def do_decode_hex(self, opts):
782 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200783 data = self._cmd.lchan.selected_file.decode_record_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100784 self._cmd.poutput_json(data, opts.oneline)
785
Harald Welteb2edd142021-01-08 23:29:35 +0100786 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100787 read_rec_parser.add_argument(
788 'record_nr', type=int, help='Number of record to be read')
789 read_rec_parser.add_argument(
790 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
791
Harald Welteb2edd142021-01-08 23:29:35 +0100792 @cmd2.with_argparser(read_rec_parser)
793 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100794 """Read one or multiple records from a record-oriented EF"""
795 for r in range(opts.count):
796 recnr = opts.record_nr + r
Harald Weltea6c0f882022-07-17 14:23:17 +0200797 (data, sw) = self._cmd.lchan.read_record(recnr)
Philipp Maier41555732021-02-25 16:52:08 +0100798 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100799 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100800 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100801 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100802 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100803
804 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100805 read_rec_dec_parser.add_argument(
806 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200807 read_rec_dec_parser.add_argument('--oneline', action='store_true',
808 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100809
Harald Welteb2edd142021-01-08 23:29:35 +0100810 @cmd2.with_argparser(read_rec_dec_parser)
811 def do_read_record_decoded(self, opts):
812 """Read + decode a record from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200813 (data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200814 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100815
Harald Welte850b72a2021-04-07 09:33:03 +0200816 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100817
Harald Welte850b72a2021-04-07 09:33:03 +0200818 @cmd2.with_argparser(read_recs_parser)
819 def do_read_records(self, opts):
820 """Read all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200821 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200822 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200823 (data, sw) = self._cmd.lchan.read_record(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200824 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100825 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200826 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100827 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200828 self._cmd.poutput("%03d %s" % (recnr, recstr))
829
830 read_recs_dec_parser = argparse.ArgumentParser()
831 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100832 help='No JSON pretty-printing, dump as a single line')
833
Harald Welte850b72a2021-04-07 09:33:03 +0200834 @cmd2.with_argparser(read_recs_dec_parser)
835 def do_read_records_decoded(self, opts):
836 """Read + decode all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200837 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200838 # collect all results in list so they are rendered as JSON list when printing
839 data_list = []
840 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200841 (data, sw) = self._cmd.lchan.read_record_dec(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200842 data_list.append(data)
843 self._cmd.poutput_json(data_list, opts.oneline)
844
Harald Welteb2edd142021-01-08 23:29:35 +0100845 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100846 upd_rec_parser.add_argument(
847 'record_nr', type=int, help='Number of record to be read')
848 upd_rec_parser.add_argument(
849 'data', help='Data bytes (hex format) to write')
850
Harald Welteb2edd142021-01-08 23:29:35 +0100851 @cmd2.with_argparser(upd_rec_parser)
852 def do_update_record(self, opts):
853 """Update (write) data to a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200854 (data, sw) = self._cmd.lchan.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100855 if data:
856 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100857
858 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100859 upd_rec_dec_parser.add_argument(
860 'record_nr', type=int, help='Number of record to be read')
861 upd_rec_dec_parser.add_argument(
862 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200863 upd_rec_dec_parser.add_argument('--json-path', type=str,
864 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100865
Harald Welteb2edd142021-01-08 23:29:35 +0100866 @cmd2.with_argparser(upd_rec_dec_parser)
867 def do_update_record_decoded(self, opts):
868 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200869 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200870 (data_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100871 js_path_modify(data_json, opts.json_path,
872 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200873 else:
874 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200875 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100876 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100877 if data:
878 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100879
Harald Welte4145d3c2021-04-08 20:34:13 +0200880 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100881 edit_rec_dec_parser.add_argument(
882 'record_nr', type=int, help='Number of record to be edited')
883
Harald Welte4145d3c2021-04-08 20:34:13 +0200884 @cmd2.with_argparser(edit_rec_dec_parser)
885 def do_edit_record_decoded(self, opts):
886 """Edit the JSON representation of one record in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200887 (orig_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200888 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200889 filename = '%s/file' % dirname
890 # write existing data as JSON to file
891 with open(filename, 'w') as text_file:
892 json.dump(orig_json, text_file, indent=4)
893 # run a text editor
894 self._cmd._run_editor(filename)
895 with open(filename, 'r') as text_file:
896 edited_json = json.load(text_file)
897 if edited_json == orig_json:
898 self._cmd.poutput("Data not modified, skipping write")
899 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200900 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100901 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200902 if data:
903 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200904
Harald Weltec91085e2022-02-10 18:05:45 +0100905 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +0100906 parent: Optional[CardDF] = None, rec_len={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200907 """
908 Args:
909 fid : File Identifier (4 hex digits)
910 sfid : Short File Identifier (2 hex digits, optional)
911 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200912 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200913 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier0adabf62021-04-20 22:36:41 +0200914 rec_len : set of {minimum_length, recommended_length}
Harald Welteee3501f2021-04-02 13:00:18 +0200915 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100916 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100917 self.rec_len = rec_len
918 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200919 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200920 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100921
Harald Weltec91085e2022-02-10 18:05:45 +0100922 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200923 """Decode raw (hex string) data into abstract representation.
924
925 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
926 method for implementing this specifically for the given file. This function checks which
927 of the method exists, add calls them (with conversion, as needed).
928
929 Args:
930 raw_hex_data : hex-encoded data
931 Returns:
932 abstract_data; dict representing the decoded data
933 """
Harald Welteb2edd142021-01-08 23:29:35 +0100934 method = getattr(self, '_decode_record_hex', None)
935 if callable(method):
936 return method(raw_hex_data)
937 raw_bin_data = h2b(raw_hex_data)
938 method = getattr(self, '_decode_record_bin', None)
939 if callable(method):
940 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200941 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200942 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200943 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100944 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100945 t.from_tlv(raw_bin_data)
946 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100947 return {'raw': raw_bin_data.hex()}
948
Harald Weltec91085e2022-02-10 18:05:45 +0100949 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200950 """Decode raw (binary) data into abstract representation.
951
952 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
953 method for implementing this specifically for the given file. This function checks which
954 of the method exists, add calls them (with conversion, as needed).
955
956 Args:
957 raw_bin_data : binary encoded data
958 Returns:
959 abstract_data; dict representing the decoded data
960 """
Harald Welteb2edd142021-01-08 23:29:35 +0100961 method = getattr(self, '_decode_record_bin', None)
962 if callable(method):
963 return method(raw_bin_data)
964 raw_hex_data = b2h(raw_bin_data)
965 method = getattr(self, '_decode_record_hex', None)
966 if callable(method):
967 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200968 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200969 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200970 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100971 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100972 t.from_tlv(raw_bin_data)
973 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100974 return {'raw': raw_hex_data}
975
Harald Weltec91085e2022-02-10 18:05:45 +0100976 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200977 """Encode abstract representation into raw (hex string) data.
978
979 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
980 method for implementing this specifically for the given file. This function checks which
981 of the method exists, add calls them (with conversion, as needed).
982
983 Args:
984 abstract_data : dict representing the decoded data
985 Returns:
986 hex string encoded data
987 """
Harald Welteb2edd142021-01-08 23:29:35 +0100988 method = getattr(self, '_encode_record_hex', None)
989 if callable(method):
990 return method(abstract_data)
991 method = getattr(self, '_encode_record_bin', None)
992 if callable(method):
993 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +0200994 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200995 if self._construct:
996 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200997 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100998 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100999 t.from_dict(abstract_data)
1000 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001001 raise NotImplementedError(
1002 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001003
Harald Weltec91085e2022-02-10 18:05:45 +01001004 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001005 """Encode abstract representation into raw (binary) data.
1006
1007 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1008 method for implementing this specifically for the given file. This function checks which
1009 of the method exists, add calls them (with conversion, as needed).
1010
1011 Args:
1012 abstract_data : dict representing the decoded data
1013 Returns:
1014 binary encoded data
1015 """
Harald Welteb2edd142021-01-08 23:29:35 +01001016 method = getattr(self, '_encode_record_bin', None)
1017 if callable(method):
1018 return method(abstract_data)
1019 method = getattr(self, '_encode_record_hex', None)
1020 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +02001021 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001022 if self._construct:
1023 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +02001024 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001025 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001026 t.from_dict(abstract_data)
1027 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001028 raise NotImplementedError(
1029 "%s encoder not yet implemented. Patches welcome." % self)
1030
Harald Welteb2edd142021-01-08 23:29:35 +01001031
1032class CyclicEF(LinFixedEF):
1033 """Cyclic EF (Entry File) in the smart card filesystem"""
1034 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +01001035
1036 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001037 rec_len={1, None}, **kwargs):
1038 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +01001039
Harald Welteb2edd142021-01-08 23:29:35 +01001040
1041class TransRecEF(TransparentEF):
1042 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +02001043
Harald Welteb2edd142021-01-08 23:29:35 +01001044 These are the real odd-balls and mostly look like mistakes in the specification:
1045 Specified as 'transparent' EF, but actually containing several fixed-length records
1046 inside.
1047 We add a special class for those, so the user only has to provide encoder/decoder functions
1048 for a record, while this class takes care of split / merge of records.
1049 """
Harald Weltec91085e2022-02-10 18:05:45 +01001050
1051 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001052 parent: Optional[CardDF] = None, size={1, None}, **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +02001053 """
1054 Args:
1055 fid : File Identifier (4 hex digits)
1056 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +02001057 name : Brief name of the file, like EF_ICCID
1058 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +02001059 parent : Parent CardFile object within filesystem hierarchy
1060 rec_len : Length of the fixed-length records within transparent EF
1061 size : tuple of (minimum_size, recommended_size)
1062 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001063 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +01001064 self.rec_len = rec_len
1065
Harald Weltec91085e2022-02-10 18:05:45 +01001066 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001067 """Decode raw (hex string) data into abstract representation.
1068
1069 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1070 method for implementing this specifically for the given file. This function checks which
1071 of the method exists, add calls them (with conversion, as needed).
1072
1073 Args:
1074 raw_hex_data : hex-encoded data
1075 Returns:
1076 abstract_data; dict representing the decoded data
1077 """
Harald Welteb2edd142021-01-08 23:29:35 +01001078 method = getattr(self, '_decode_record_hex', None)
1079 if callable(method):
1080 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001081 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001082 method = getattr(self, '_decode_record_bin', None)
1083 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001084 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001085 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001086 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001087 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001088 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001089 t.from_tlv(raw_bin_data)
1090 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001091 return {'raw': raw_hex_data}
1092
Harald Weltec91085e2022-02-10 18:05:45 +01001093 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001094 """Decode raw (binary) data into abstract representation.
1095
1096 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1097 method for implementing this specifically for the given file. This function checks which
1098 of the method exists, add calls them (with conversion, as needed).
1099
1100 Args:
1101 raw_bin_data : binary encoded data
1102 Returns:
1103 abstract_data; dict representing the decoded data
1104 """
Harald Welteb2edd142021-01-08 23:29:35 +01001105 method = getattr(self, '_decode_record_bin', None)
1106 if callable(method):
1107 return method(raw_bin_data)
1108 raw_hex_data = b2h(raw_bin_data)
1109 method = getattr(self, '_decode_record_hex', None)
1110 if callable(method):
1111 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001112 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001113 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001114 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001115 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001116 t.from_tlv(raw_bin_data)
1117 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001118 return {'raw': raw_hex_data}
1119
Harald Weltec91085e2022-02-10 18:05:45 +01001120 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001121 """Encode abstract representation into raw (hex string) data.
1122
1123 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1124 method for implementing this specifically for the given file. This function checks which
1125 of the method exists, add calls them (with conversion, as needed).
1126
1127 Args:
1128 abstract_data : dict representing the decoded data
1129 Returns:
1130 hex string encoded data
1131 """
Harald Welteb2edd142021-01-08 23:29:35 +01001132 method = getattr(self, '_encode_record_hex', None)
1133 if callable(method):
1134 return method(abstract_data)
1135 method = getattr(self, '_encode_record_bin', None)
1136 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001137 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001138 if self._construct:
1139 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001140 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001141 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001142 t.from_dict(abstract_data)
1143 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001144 raise NotImplementedError(
1145 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001146
Harald Weltec91085e2022-02-10 18:05:45 +01001147 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001148 """Encode abstract representation into raw (binary) data.
1149
1150 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1151 method for implementing this specifically for the given file. This function checks which
1152 of the method exists, add calls them (with conversion, as needed).
1153
1154 Args:
1155 abstract_data : dict representing the decoded data
1156 Returns:
1157 binary encoded data
1158 """
Harald Welteb2edd142021-01-08 23:29:35 +01001159 method = getattr(self, '_encode_record_bin', None)
1160 if callable(method):
1161 return method(abstract_data)
1162 method = getattr(self, '_encode_record_hex', None)
1163 if callable(method):
1164 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001165 if self._construct:
1166 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001167 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001168 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001169 t.from_dict(abstract_data)
1170 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001171 raise NotImplementedError(
1172 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001173
Harald Weltec91085e2022-02-10 18:05:45 +01001174 def _decode_bin(self, raw_bin_data: bytearray):
1175 chunks = [raw_bin_data[i:i+self.rec_len]
1176 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001177 return [self.decode_record_bin(x) for x in chunks]
1178
Harald Welteee3501f2021-04-02 13:00:18 +02001179 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001180 chunks = [self.encode_record_bin(x) for x in abstract_data]
1181 # FIXME: pad to file size
1182 return b''.join(chunks)
1183
1184
Harald Welte917d98c2021-04-21 11:51:25 +02001185class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001186 """BER-TLV EF (Entry File) in the smart card filesystem.
1187 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001188
Harald Welte27881622021-04-21 11:16:31 +02001189 NOTE: We currently don't really support those, this class is simply a wrapper
1190 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1191 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001192
Harald Welte917d98c2021-04-21 11:51:25 +02001193 @with_default_category('BER-TLV EF Commands')
1194 class ShellCommands(CommandSet):
1195 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001196
Harald Welte917d98c2021-04-21 11:51:25 +02001197 def __init__(self):
1198 super().__init__()
1199
1200 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001201 retrieve_data_parser.add_argument(
1202 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1203
Harald Welte917d98c2021-04-21 11:51:25 +02001204 @cmd2.with_argparser(retrieve_data_parser)
1205 def do_retrieve_data(self, opts):
1206 """Retrieve (Read) data from a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001207 (data, sw) = self._cmd.lchan.retrieve_data(opts.tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001208 self._cmd.poutput(data)
1209
1210 def do_retrieve_tags(self, opts):
1211 """List tags available in a given BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001212 tags = self._cmd.lchan.retrieve_tags()
Harald Welte917d98c2021-04-21 11:51:25 +02001213 self._cmd.poutput(tags)
1214
1215 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001216 set_data_parser.add_argument(
1217 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1218 set_data_parser.add_argument(
1219 'data', help='Data bytes (hex format) to write')
1220
Harald Welte917d98c2021-04-21 11:51:25 +02001221 @cmd2.with_argparser(set_data_parser)
1222 def do_set_data(self, opts):
1223 """Set (Write) data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001224 (data, sw) = self._cmd.lchan.set_data(opts.tag, opts.data)
Harald Welte917d98c2021-04-21 11:51:25 +02001225 if data:
1226 self._cmd.poutput(data)
1227
1228 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001229 del_data_parser.add_argument(
1230 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1231
Harald Welte917d98c2021-04-21 11:51:25 +02001232 @cmd2.with_argparser(del_data_parser)
1233 def do_delete_data(self, opts):
1234 """Delete data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001235 (data, sw) = self._cmd.lchan.set_data(opts.tag, None)
Harald Welte917d98c2021-04-21 11:51:25 +02001236 if data:
1237 self._cmd.poutput(data)
1238
Harald Weltec91085e2022-02-10 18:05:45 +01001239 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte9170fbf2022-02-11 21:54:37 +01001240 size={1, None}, **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001241 """
1242 Args:
1243 fid : File Identifier (4 hex digits)
1244 sfid : Short File Identifier (2 hex digits, optional)
1245 name : Brief name of the file, lik EF_ICCID
1246 desc : Description of the file
1247 parent : Parent CardFile object within filesystem hierarchy
1248 size : tuple of (minimum_size, recommended_size)
1249 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001250 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001251 self._construct = None
1252 self.size = size
1253 self.shell_commands = [self.ShellCommands()]
1254
Harald Welteb2edd142021-01-08 23:29:35 +01001255
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001256class RuntimeState:
Harald Welteb2edd142021-01-08 23:29:35 +01001257 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001258
1259 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001260 """
1261 Args:
1262 card : pysim.cards.Card instance
1263 profile : CardProfile instance
1264 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001265 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001266 self.card = card
Harald Welteb2edd142021-01-08 23:29:35 +01001267 self.profile = profile
Harald Weltea6c0f882022-07-17 14:23:17 +02001268 self.lchan = {}
1269 # the basic logical channel always exists
1270 self.lchan[0] = RuntimeLchan(0, self)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001271
1272 # make sure the class and selection control bytes, which are specified
1273 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001274 self.card.set_apdu_parameter(
1275 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001276
Harald Welte5ce35242021-04-02 20:27:05 +02001277 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001278 apps = self._match_applications()
1279 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001280 if a.adf:
1281 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001282 for f in self.profile.files_in_mf:
1283 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001284 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001285
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001286 # make sure that when the runtime state is created, the card is also
1287 # in a defined state.
1288 self.reset()
1289
Philipp Maier1e896f32021-03-10 17:02:53 +01001290 def _match_applications(self):
1291 """match the applications from the profile with applications on the card"""
1292 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001293
1294 # When the profile does not feature any applications, then we are done already
1295 if not apps_profile:
1296 return []
1297
1298 # Read AIDs from card and match them against the applications defined by the
1299 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001300 aids_card = self.card.read_aids()
1301 apps_taken = []
1302 if aids_card:
1303 aids_taken = []
1304 print("AIDs on card:")
1305 for a in aids_card:
1306 for f in apps_profile:
1307 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001308 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001309 aids_taken.append(a)
1310 apps_taken.append(f)
1311 aids_unknown = set(aids_card) - set(aids_taken)
1312 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001313 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001314 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001315 print("warning: EF.DIR seems to be empty!")
1316
1317 # Some card applications may not be registered in EF.DIR, we will actively
1318 # probe for those applications
1319 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001320 try:
1321 data, sw = self.card.select_adf_by_aid(f.aid)
1322 if sw == "9000":
1323 print(" %s: %s" % (f.name, f.aid))
1324 apps_taken.append(f)
1325 except SwMatchError:
1326 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001327 return apps_taken
1328
Harald Weltea6c0f882022-07-17 14:23:17 +02001329 def reset(self, cmd_app=None) -> Hexstr:
1330 """Perform physical card reset and obtain ATR.
1331 Args:
1332 cmd_app : Command Application State (for unregistering old file commands)
1333 """
1334 # delete all lchan != 0 (basic lchan)
1335 for lchan_nr in self.lchan.keys():
1336 if lchan_nr == 0:
1337 continue
1338 del self.lchan[lchan_nr]
1339 atr = i2h(self.card.reset())
1340 # select MF to reset internal state and to verify card really works
1341 self.lchan[0].select('MF', cmd_app)
1342 self.lchan[0].selected_adf = None
1343 return atr
1344
1345 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1346 """Add a logical channel to the runtime state. You shouldn't call this
1347 directly but always go through RuntimeLchan.add_lchan()."""
1348 if lchan_nr in self.lchan.keys():
1349 raise ValueError('Cannot create already-existing lchan %d' % lchan_nr)
1350 self.lchan[lchan_nr] = RuntimeLchan(lchan_nr, self)
1351 return self.lchan[lchan_nr]
1352
1353 def del_lchan(self, lchan_nr: int):
1354 if lchan_nr in self.lchan.keys():
1355 del self.lchan[lchan_nr]
1356 return True
1357 else:
1358 return False
1359
1360 def get_lchan_by_cla(self, cla) -> Optional['RuntimeLchan']:
1361 lchan_nr = lchan_nr_from_cla(cla)
1362 if lchan_nr in self.lchan.keys():
1363 return self.lchan[lchan_nr]
1364 else:
1365 return None
1366
1367
1368class RuntimeLchan:
1369 """Represent the runtime state of a logical channel with a card."""
1370
1371 def __init__(self, lchan_nr: int, rs: RuntimeState):
1372 self.lchan_nr = lchan_nr
1373 self.rs = rs
1374 self.selected_file = self.rs.mf
1375 self.selected_adf = None
1376 self.selected_file_fcp = None
1377 self.selected_file_fcp_hex = None
1378
1379 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1380 """Add a new logical channel from the current logical channel. Just affects
1381 internal state, doesn't actually open a channel with the UICC."""
1382 new_lchan = self.rs.add_lchan(lchan_nr)
1383 # See TS 102 221 Table 8.3
1384 if self.lchan_nr != 0:
1385 new_lchan.selected_file = self.get_cwd()
1386 new_lchan.selected_adf = self.selected_adf
1387 return new_lchan
1388
Harald Welte747a9782022-02-13 17:52:28 +01001389 def selected_file_descriptor_byte(self) -> dict:
1390 return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
1391
1392 def selected_file_shareable(self) -> bool:
1393 return self.selected_file_descriptor_byte()['shareable']
1394
1395 def selected_file_structure(self) -> str:
1396 return self.selected_file_descriptor_byte()['structure']
1397
1398 def selected_file_type(self) -> str:
1399 return self.selected_file_descriptor_byte()['file_type']
1400
1401 def selected_file_num_of_rec(self) -> Optional[int]:
1402 return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
1403
Harald Welteee3501f2021-04-02 13:00:18 +02001404 def get_cwd(self) -> CardDF:
1405 """Obtain the current working directory.
1406
1407 Returns:
1408 CardDF instance
1409 """
Harald Welteb2edd142021-01-08 23:29:35 +01001410 if isinstance(self.selected_file, CardDF):
1411 return self.selected_file
1412 else:
1413 return self.selected_file.parent
1414
Harald Welte5ce35242021-04-02 20:27:05 +02001415 def get_application_df(self) -> Optional[CardADF]:
1416 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001417
1418 Returns:
1419 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001420 # iterate upwards from selected file; check if any is an ADF
1421 node = self.selected_file
1422 while node.parent != node:
1423 if isinstance(node, CardADF):
1424 return node
1425 node = node.parent
1426 return None
1427
Harald Weltec91085e2022-02-10 18:05:45 +01001428 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001429 """Interpret a given status word relative to the currently selected application
1430 or the underlying card profile.
1431
1432 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001433 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001434
1435 Returns:
1436 Tuple of two strings
1437 """
Harald Welte86fbd392021-04-02 22:13:09 +02001438 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001439 adf = self.get_application_df()
1440 if adf:
1441 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001442 # The application either comes with its own interpret_sw
1443 # method or we will use the interpret_sw method from the
1444 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001445 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001446 res = app.interpret_sw(sw)
Harald Weltea6c0f882022-07-17 14:23:17 +02001447 return res or self.rs.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001448
Harald Weltec91085e2022-02-10 18:05:45 +01001449 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001450 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001451 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001452 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001453 raise ValueError(
1454 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001455
1456 try:
Harald Weltea6c0f882022-07-17 14:23:17 +02001457 (data, sw) = self.rs.card._scc.select_file(fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001458 except SwMatchError as swm:
1459 k = self.interpret_sw(swm.sw_actual)
1460 if not k:
1461 raise(swm)
1462 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1463
1464 select_resp = self.selected_file.decode_select_response(data)
Harald Welte747a9782022-02-13 17:52:28 +01001465 if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001466 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1467 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001468 else:
Harald Welte747a9782022-02-13 17:52:28 +01001469 if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001470 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1471 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001472 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001473 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1474 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001475
1476 self.selected_file.add_files([f])
1477 self.selected_file = f
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001478 return select_resp, data
Philipp Maier63f572d2021-03-09 22:42:47 +01001479
Harald Welteaceb2a52022-02-12 21:41:59 +01001480 def _select_pre(self, cmd_app):
1481 # unregister commands of old file
1482 if cmd_app and self.selected_file.shell_commands:
1483 for c in self.selected_file.shell_commands:
1484 cmd_app.unregister_command_set(c)
1485
1486 def _select_post(self, cmd_app):
1487 # register commands of new file
1488 if cmd_app and self.selected_file.shell_commands:
1489 for c in self.selected_file.shell_commands:
1490 cmd_app.register_command_set(c)
1491
1492 def select_file(self, file: CardFile, cmd_app=None):
1493 """Select a file (EF, DF, ADF, MF, ...).
1494
1495 Args:
1496 file : CardFile [or derived class] instance
1497 cmd_app : Command Application State (for unregistering old file commands)
1498 """
1499 # we need to find a path from our self.selected_file to the destination
1500 inter_path = self.selected_file.build_select_path_to(file)
1501 if not inter_path:
1502 raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
1503
1504 self._select_pre(cmd_app)
1505
1506 for p in inter_path:
1507 try:
1508 if isinstance(p, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001509 (data, sw) = self.rs.card.select_adf_by_aid(p.aid)
Harald Welte46a7a3f2022-07-16 11:47:47 +02001510 self.selected_adf = p
Harald Welteaceb2a52022-02-12 21:41:59 +01001511 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001512 (data, sw) = self.rs.card._scc.select_file(p.fid)
Harald Welteaceb2a52022-02-12 21:41:59 +01001513 self.selected_file = p
1514 except SwMatchError as swm:
1515 self._select_post(cmd_app)
1516 raise(swm)
1517
1518 self._select_post(cmd_app)
1519
Harald Weltec91085e2022-02-10 18:05:45 +01001520 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001521 """Select a file (EF, DF, ADF, MF, ...).
1522
1523 Args:
1524 name : Name of file to select
1525 cmd_app : Command Application State (for unregistering old file commands)
1526 """
Harald Welteee670bc2022-02-13 15:10:15 +01001527 # handling of entire paths with multiple directories/elements
1528 if '/' in name:
1529 prev_sel_file = self.selected_file
1530 pathlist = name.split('/')
1531 # treat /DF.GSM/foo like MF/DF.GSM/foo
1532 if pathlist[0] == '':
1533 pathlist[0] = 'MF'
1534 try:
1535 for p in pathlist:
1536 self.select(p, cmd_app)
1537 return
1538 except Exception as e:
1539 # if any intermediate step fails, go back to where we were
1540 self.select_file(prev_sel_file, cmd_app)
1541 raise e
1542
Harald Welteb2edd142021-01-08 23:29:35 +01001543 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001544 if is_hex(name):
1545 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001546
Harald Welteaceb2a52022-02-12 21:41:59 +01001547 self._select_pre(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001548
Harald Welteb2edd142021-01-08 23:29:35 +01001549 if name in sels:
1550 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001551 try:
1552 if isinstance(f, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001553 (data, sw) = self.rs.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001554 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001555 (data, sw) = self.rs.card._scc.select_file(f.fid)
Harald Welteb2edd142021-01-08 23:29:35 +01001556 self.selected_file = f
1557 except SwMatchError as swm:
1558 k = self.interpret_sw(swm.sw_actual)
1559 if not k:
1560 raise(swm)
1561 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001562 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001563 else:
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001564 (select_resp, data) = self.probe_file(name, cmd_app)
1565
Harald Welte2bb17f32022-02-15 15:41:55 +01001566 # store the raw + decoded FCP for later reference
1567 self.selected_file_fcp_hex = data
Harald Welte850b72a2021-04-07 09:33:03 +02001568 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001569
Harald Welteaceb2a52022-02-12 21:41:59 +01001570 self._select_post(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001571 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001572
Harald Welte34b05d32021-05-25 22:03:13 +02001573 def status(self):
1574 """Request STATUS (current selected file FCP) from card."""
Harald Weltea6c0f882022-07-17 14:23:17 +02001575 (data, sw) = self.rs.card._scc.status()
Harald Welte34b05d32021-05-25 22:03:13 +02001576 return self.selected_file.decode_select_response(data)
1577
Harald Welte3c9b7842021-10-19 21:44:24 +02001578 def get_file_for_selectable(self, name: str):
1579 sels = self.selected_file.get_selectables()
1580 return sels[name]
1581
Harald Weltec91085e2022-02-10 18:05:45 +01001582 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001583 """Request ACTIVATE FILE of specified file."""
1584 sels = self.selected_file.get_selectables()
1585 f = sels[name]
Harald Weltea6c0f882022-07-17 14:23:17 +02001586 data, sw = self.rs.card._scc.activate_file(f.fid)
Harald Welte485692b2021-05-25 22:21:44 +02001587 return data, sw
1588
Harald Weltec91085e2022-02-10 18:05:45 +01001589 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001590 """Read [part of] a transparent EF binary data.
1591
1592 Args:
1593 length : Amount of data to read (None: as much as possible)
1594 offset : Offset into the file from which to read 'length' bytes
1595 Returns:
1596 binary data read from the file
1597 """
Harald Welteb2edd142021-01-08 23:29:35 +01001598 if not isinstance(self.selected_file, TransparentEF):
1599 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001600 return self.rs.card._scc.read_binary(self.selected_file.fid, length, offset)
Harald Welteb2edd142021-01-08 23:29:35 +01001601
Harald Welte2d4a64b2021-04-03 09:01:24 +02001602 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001603 """Read [part of] a transparent EF binary data and decode it.
1604
1605 Args:
1606 length : Amount of data to read (None: as much as possible)
1607 offset : Offset into the file from which to read 'length' bytes
1608 Returns:
1609 abstract decode data read from the file
1610 """
Harald Welteb2edd142021-01-08 23:29:35 +01001611 (data, sw) = self.read_binary()
1612 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001613 return (dec_data, sw)
1614
Harald Weltec91085e2022-02-10 18:05:45 +01001615 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001616 """Update transparent EF binary data.
1617
1618 Args:
1619 data_hex : hex string of data to be written
1620 offset : Offset into the file from which to write 'data_hex'
1621 """
Harald Welteb2edd142021-01-08 23:29:35 +01001622 if not isinstance(self.selected_file, TransparentEF):
1623 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001624 return self.rs.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.rs.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001625
Harald Weltec91085e2022-02-10 18:05:45 +01001626 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001627 """Update transparent EF from abstract data. Encodes the data to binary and
1628 then updates the EF with it.
1629
1630 Args:
1631 data : abstract data which is to be encoded and written
1632 """
Harald Welteb2edd142021-01-08 23:29:35 +01001633 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001634 return self.update_binary(data_hex)
1635
Harald Weltec91085e2022-02-10 18:05:45 +01001636 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001637 """Read a record as binary data.
1638
1639 Args:
1640 rec_nr : Record number to read
1641 Returns:
1642 hex string of binary data contained in record
1643 """
Harald Welteb2edd142021-01-08 23:29:35 +01001644 if not isinstance(self.selected_file, LinFixedEF):
1645 raise TypeError("Only works with Linear Fixed EF")
1646 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001647 return self.rs.card._scc.read_record(self.selected_file.fid, rec_nr)
Harald Welteb2edd142021-01-08 23:29:35 +01001648
Harald Weltec91085e2022-02-10 18:05:45 +01001649 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001650 """Read a record and decode it to abstract data.
1651
1652 Args:
1653 rec_nr : Record number to read
1654 Returns:
1655 abstract data contained in record
1656 """
Harald Welteb2edd142021-01-08 23:29:35 +01001657 (data, sw) = self.read_record(rec_nr)
1658 return (self.selected_file.decode_record_hex(data), sw)
1659
Harald Weltec91085e2022-02-10 18:05:45 +01001660 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001661 """Update a record with given binary data
1662
1663 Args:
1664 rec_nr : Record number to read
1665 data_hex : Hex string binary data to be written
1666 """
Harald Welteb2edd142021-01-08 23:29:35 +01001667 if not isinstance(self.selected_file, LinFixedEF):
1668 raise TypeError("Only works with Linear Fixed EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001669 return self.rs.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.rs.conserve_write)
Harald Welteb2edd142021-01-08 23:29:35 +01001670
Harald Weltec91085e2022-02-10 18:05:45 +01001671 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001672 """Update a record with given abstract data. Will encode abstract to binary data
1673 and then write it to the given record on the card.
1674
1675 Args:
1676 rec_nr : Record number to read
1677 data_hex : Abstract data to be written
1678 """
Harald Welte1e456572021-04-02 17:16:30 +02001679 data_hex = self.selected_file.encode_record_hex(data)
1680 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001681
Harald Weltec91085e2022-02-10 18:05:45 +01001682 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001683 """Read a DO/TLV as binary data.
1684
1685 Args:
1686 tag : Tag of TLV/DO to read
1687 Returns:
1688 hex string of full BER-TLV DO including Tag and Length
1689 """
1690 if not isinstance(self.selected_file, BerTlvEF):
1691 raise TypeError("Only works with BER-TLV EF")
1692 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001693 return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001694
1695 def retrieve_tags(self):
1696 """Retrieve tags available on BER-TLV EF.
1697
1698 Returns:
1699 list of integer tags contained in EF
1700 """
1701 if not isinstance(self.selected_file, BerTlvEF):
1702 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001703 data, sw = self.rs.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001704 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001705 return list(value)
1706
Harald Weltec91085e2022-02-10 18:05:45 +01001707 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001708 """Update a TLV/DO with given binary data
1709
1710 Args:
1711 tag : Tag of TLV/DO to be written
1712 data_hex : Hex string binary data to be written (value portion)
1713 """
1714 if not isinstance(self.selected_file, BerTlvEF):
1715 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001716 return self.rs.card._scc.set_data(self.selected_file.fid, tag, data_hex, conserve=self.rs.conserve_write)
Harald Welte917d98c2021-04-21 11:51:25 +02001717
Philipp Maier5d698e52021-09-16 13:18:01 +02001718 def unregister_cmds(self, cmd_app=None):
1719 """Unregister all file specific commands."""
1720 if cmd_app and self.selected_file.shell_commands:
1721 for c in self.selected_file.shell_commands:
1722 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001723
Harald Welteb2edd142021-01-08 23:29:35 +01001724
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001725class FileData:
Harald Welteb2edd142021-01-08 23:29:35 +01001726 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001727
Harald Welteb2edd142021-01-08 23:29:35 +01001728 def __init__(self, fdesc):
1729 self.desc = fdesc
1730 self.fcp = None
1731
1732
Harald Weltec91085e2022-02-10 18:05:45 +01001733def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001734 """Interpret a given status word.
1735
1736 Args:
1737 sw_data : Hierarchical dict of status word matches
1738 sw : status word to match (string of 4 hex digits)
1739 Returns:
1740 tuple of two strings (class_string, description)
1741 """
Harald Welteb2edd142021-01-08 23:29:35 +01001742 for class_str, swdict in sw_data.items():
1743 # first try direct match
1744 if sw in swdict:
1745 return (class_str, swdict[sw])
1746 # next try wildcard matches
1747 for pattern, descr in swdict.items():
1748 if sw_match(sw, pattern):
1749 return (class_str, descr)
1750 return None
1751
Harald Weltec91085e2022-02-10 18:05:45 +01001752
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001753class CardApplication:
Harald Welteb2edd142021-01-08 23:29:35 +01001754 """A card application is represented by an ADF (with contained hierarchy) and optionally
1755 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001756
1757 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001758 """
1759 Args:
1760 adf : ADF name
1761 sw : Dict of status word conversions
1762 """
Harald Welteb2edd142021-01-08 23:29:35 +01001763 self.name = name
1764 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001765 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001766 # back-reference from ADF to Applicaiton
1767 if self.adf:
1768 self.aid = aid or self.adf.aid
1769 self.adf.application = self
1770 else:
1771 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001772
1773 def __str__(self):
1774 return "APP(%s)" % (self.name)
1775
1776 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001777 """Interpret a given status word within the application.
1778
1779 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001780 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001781
1782 Returns:
1783 Tuple of two strings
1784 """
Harald Welteb2edd142021-01-08 23:29:35 +01001785 return interpret_sw(self.sw, sw)
1786
Harald Weltef44256c2021-10-14 15:53:39 +02001787
1788class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001789 """A specific card model, typically having some additional vendor-specific files. All
1790 you need to do is to define a sub-class with a list of ATRs or an overridden match
1791 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001792 _atrs = []
1793
1794 @classmethod
1795 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001796 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001797 """Add model specific files to given RuntimeState."""
1798
1799 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001800 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001801 """Test if given card matches this model."""
1802 card_atr = scc.get_atr()
1803 for atr in cls._atrs:
1804 atr_bin = toBytes(atr)
1805 if atr_bin == card_atr:
1806 print("Detected CardModel:", cls.__name__)
1807 return True
1808 return False
1809
1810 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001811 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001812 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1813 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1814 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001815 for m in CardModel.__subclasses__():
1816 if m.match(scc):
1817 m.add_files(rs)