blob: 22ff60dc894b2f07cf9cc340dc828d1fb2498252 [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 Welte13edf302022-07-21 15:19:23 +020052Size = Tuple[int, Optional[int]]
53
Harald Weltea6c0f882022-07-17 14:23:17 +020054def lchan_nr_from_cla(cla: int) -> int:
55 """Resolve the logical channel number from the CLA byte."""
56 # TS 102 221 10.1.1 Coding of Class Byte
57 if cla >> 4 in [0x0, 0xA, 0x8]:
58 # Table 10.3
59 return cla & 0x03
60 elif cla & 0xD0 in [0x40, 0xC0]:
61 # Table 10.4a
62 return 4 + (cla & 0x0F)
63 else:
64 raise ValueError('Could not determine logical channel for CLA=%2X' % cla)
65
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +070066class CardFile:
Harald Welteb2edd142021-01-08 23:29:35 +010067 """Base class for all objects in the smart card filesystem.
68 Serve as a common ancestor to all other file types; rarely used directly.
69 """
70 RESERVED_NAMES = ['..', '.', '/', 'MF']
71 RESERVED_FIDS = ['3f00']
72
Harald Weltec91085e2022-02-10 18:05:45 +010073 def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
Harald Welte9170fbf2022-02-11 21:54:37 +010074 parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None,
75 service: Optional[CardFileService] = None):
Harald Welteee3501f2021-04-02 13:00:18 +020076 """
77 Args:
78 fid : File Identifier (4 hex digits)
79 sfid : Short File Identifier (2 hex digits, optional)
80 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +020081 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +020082 parent : Parent CardFile object within filesystem hierarchy
Philipp Maier5af7bdf2021-11-04 12:48:41 +010083 profile : Card profile that this file should be part of
Harald Welte9170fbf2022-02-11 21:54:37 +010084 service : Service (SST/UST/IST) associated with the file
Harald Welteee3501f2021-04-02 13:00:18 +020085 """
Harald Welteb2edd142021-01-08 23:29:35 +010086 if not isinstance(self, CardADF) and fid == None:
87 raise ValueError("fid is mandatory")
88 if fid:
89 fid = fid.lower()
90 self.fid = fid # file identifier
91 self.sfid = sfid # short file identifier
92 self.name = name # human readable name
93 self.desc = desc # human readable description
94 self.parent = parent
95 if self.parent and self.parent != self and self.fid:
96 self.parent.add_file(self)
Philipp Maier5af7bdf2021-11-04 12:48:41 +010097 self.profile = profile
Harald Welte9170fbf2022-02-11 21:54:37 +010098 self.service = service
Harald Weltec91085e2022-02-10 18:05:45 +010099 self.shell_commands = [] # type: List[CommandSet]
Harald Welteb2edd142021-01-08 23:29:35 +0100100
Harald Weltec91085e2022-02-10 18:05:45 +0100101 # Note: the basic properties (fid, name, ect.) are verified when
102 # the file is attached to a parent file. See method add_file() in
103 # class Card DF
Philipp Maier66061582021-03-09 21:57:57 +0100104
Harald Welteb2edd142021-01-08 23:29:35 +0100105 def __str__(self):
106 if self.name:
107 return self.name
108 else:
109 return self.fid
110
Harald Weltec91085e2022-02-10 18:05:45 +0100111 def _path_element(self, prefer_name: bool) -> Optional[str]:
Harald Welteb2edd142021-01-08 23:29:35 +0100112 if prefer_name and self.name:
113 return self.name
114 else:
115 return self.fid
116
Harald Welteb2e4b4a2022-07-19 23:48:45 +0200117 def fully_qualified_path_str(self, prefer_name: bool = True) -> str:
118 """Return fully qualified path to file as string.
119
120 Args:
121 prefer_name : Preferably build path of names; fall-back to FIDs as required
122 """
123 return '/'.join(self.fully_qualified_path(prefer_name))
124
Harald Weltec91085e2022-02-10 18:05:45 +0100125 def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200126 """Return fully qualified path to file as list of FID or name strings.
127
128 Args:
129 prefer_name : Preferably build path of names; fall-back to FIDs as required
130 """
Harald Welte1e456572021-04-02 17:16:30 +0200131 if self.parent and self.parent != self:
Harald Welteb2edd142021-01-08 23:29:35 +0100132 ret = self.parent.fully_qualified_path(prefer_name)
133 else:
134 ret = []
Harald Welte1e456572021-04-02 17:16:30 +0200135 elem = self._path_element(prefer_name)
136 if elem:
137 ret.append(elem)
Harald Welteb2edd142021-01-08 23:29:35 +0100138 return ret
139
Harald Welteaceb2a52022-02-12 21:41:59 +0100140 def fully_qualified_path_fobj(self) -> List['CardFile']:
141 """Return fully qualified path to file as list of CardFile instance references."""
142 if self.parent and self.parent != self:
143 ret = self.parent.fully_qualified_path_fobj()
144 else:
145 ret = []
146 if self:
147 ret.append(self)
148 return ret
149
150 def build_select_path_to(self, target: 'CardFile') -> Optional[List['CardFile']]:
151 """Build the relative sequence of files we need to traverse to get from us to 'target'."""
Harald Welte02a7f742023-07-11 09:21:52 +0200152 # special-case handling for selecting MF while we MF is selected
153 if target == target.get_mf():
154 return [target]
Harald Welteaceb2a52022-02-12 21:41:59 +0100155 cur_fqpath = self.fully_qualified_path_fobj()
156 target_fqpath = target.fully_qualified_path_fobj()
157 inter_path = []
158 cur_fqpath.pop() # drop last element (currently selected file, doesn't need re-selection
159 cur_fqpath.reverse()
160 for ce in cur_fqpath:
161 inter_path.append(ce)
162 for i in range(0, len(target_fqpath)-1):
163 te = target_fqpath[i]
164 if te == ce:
165 for te2 in target_fqpath[i+1:]:
166 inter_path.append(te2)
167 # we found our common ancestor
168 return inter_path
169 return None
170
Harald Welteee3501f2021-04-02 13:00:18 +0200171 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100172 """Return the MF (root) of the file system."""
173 if self.parent == None:
174 return None
175 # iterate towards the top. MF has parent == self
176 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200177 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100178 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200179 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100180
Harald Weltec91085e2022-02-10 18:05:45 +0100181 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200182 """Return a dict of {'identifier': self} tuples.
183
184 Args:
185 alias : Add an alias with given name to 'self'
186 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
187 If not specified, all selectables will be returned.
188 Returns:
189 dict containing reference to 'self' for all identifiers.
190 """
Harald Welteb2edd142021-01-08 23:29:35 +0100191 sels = {}
192 if alias:
193 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100194 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100195 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100196 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100197 sels.update({self.name: self})
198 return sels
199
Harald Weltef5ff1b82022-07-24 12:19:57 +0200200 def _get_parent_selectables(self, alias: Optional[str] = None, flags=[]) -> Dict[str, 'CardFile']:
201 sels = {}
202 if not self.parent or self.parent == self:
203 return sels
204 # add our immediate parent
205 if alias:
206 sels.update({alias: self.parent})
207 if self.parent.fid and (flags == [] or 'FIDS' in flags):
208 sels.update({self.parent.fid: self.parent})
209 if self.parent.name and (flags == [] or 'FNAMES' in flags):
210 sels.update({self.parent.name: self.parent})
211 # recurse to parents of our parent, but without any alias
212 sels.update(self.parent._get_parent_selectables(None, flags))
213 return sels
214
Harald Weltec91085e2022-02-10 18:05:45 +0100215 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200216 """Return a dict of {'identifier': File} that is selectable from the current file.
217
218 Args:
219 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
220 If not specified, all selectables will be returned.
221 Returns:
222 dict containing all selectable items. Key is identifier (string), value
223 a reference to a CardFile (or derived class) instance.
224 """
Philipp Maier786f7812021-02-25 16:48:10 +0100225 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100226 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100227 if flags == [] or 'SELF' in flags:
228 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100229 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100230 if flags == [] or 'PARENT' in flags:
Harald Weltef5ff1b82022-07-24 12:19:57 +0200231 sels.update(self._get_parent_selectables('..', flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100232 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100233 if flags == [] or 'MF' in flags:
234 mf = self.get_mf()
235 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100236 sels.update(mf._get_self_selectables(flags=flags))
237 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100238 return sels
239
Harald Weltec91085e2022-02-10 18:05:45 +0100240 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200241 """Return a dict of {'identifier': File} that is selectable from the current file.
242
243 Args:
244 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
245 If not specified, all selectables will be returned.
246 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200247 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200248 """
Philipp Maier786f7812021-02-25 16:48:10 +0100249 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100250 sel_keys = list(sels.keys())
251 sel_keys.sort()
252 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100253
Harald Weltec91085e2022-02-10 18:05:45 +0100254 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100255 """Decode the response to a SELECT command.
256
257 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100258 data_hex: Hex string of the select response
259 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100260
Harald Weltec91085e2022-02-10 18:05:45 +0100261 # When the current file does not implement a custom select response decoder,
262 # we just ask the parent file to decode the select response. If this method
263 # is not overloaded by the current file we will again ask the parent file.
264 # This way we recursively travel up the file system tree until we hit a file
265 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200266 if self.parent:
267 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100268
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100269 def get_profile(self):
270 """Get the profile associated with this file. If this file does not have any
271 profile assigned, try to find a file above (usually the MF) in the filesystem
272 hirarchy that has a profile assigned
273 """
274
275 # If we have a profile set, return it
276 if self.profile:
277 return self.profile
278
279 # Walk up recursively until we hit a parent that has a profile set
280 if self.parent:
281 return self.parent.get_profile()
282 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100283
Harald Welte9170fbf2022-02-11 21:54:37 +0100284 def should_exist_for_services(self, services: List[int]):
285 """Assuming the provided list of activated services, should this file exist and be activated?."""
286 if self.service is None:
287 return None
288 elif isinstance(self.service, int):
289 # a single service determines the result
290 return self.service in services
291 elif isinstance(self.service, list):
292 # any of the services active -> true
293 for s in self.service:
294 if s in services:
295 return True
296 return False
297 elif isinstance(self.service, tuple):
298 # all of the services active -> true
299 for s in self.service:
300 if not s in services:
301 return False
302 return True
303 else:
304 raise ValueError("self.service must be either int or list or tuple")
305
Harald Weltec91085e2022-02-10 18:05:45 +0100306
Harald Welteb2edd142021-01-08 23:29:35 +0100307class CardDF(CardFile):
308 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100309
310 @with_default_category('DF/ADF Commands')
311 class ShellCommands(CommandSet):
312 def __init__(self):
313 super().__init__()
314
Harald Welteb2edd142021-01-08 23:29:35 +0100315 def __init__(self, **kwargs):
316 if not isinstance(self, CardADF):
317 if not 'fid' in kwargs:
318 raise TypeError('fid is mandatory for all DF')
319 super().__init__(**kwargs)
320 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100321 self.shell_commands = [self.ShellCommands()]
Harald Welte9170fbf2022-02-11 21:54:37 +0100322 # dict of CardFile affected by service(int), indexed by service
323 self.files_by_service = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100324
325 def __str__(self):
326 return "DF(%s)" % (super().__str__())
327
Harald Welte9170fbf2022-02-11 21:54:37 +0100328 def _add_file_services(self, child):
329 """Add a child (DF/EF) to the files_by_services of the parent."""
330 if not child.service:
331 return
332 if isinstance(child.service, int):
333 self.files_by_service.setdefault(child.service, []).append(child)
334 elif isinstance(child.service, list):
335 for service in child.service:
336 self.files_by_service.setdefault(service, []).append(child)
337 elif isinstance(child.service, tuple):
338 for service in child.service:
339 self.files_by_service.setdefault(service, []).append(child)
340 else:
341 raise ValueError
342
Harald Welted56f45d2022-07-16 11:46:59 +0200343 def _has_service(self):
344 if self.service:
345 return True
346 for c in self.children.values():
347 if isinstance(c, CardDF):
348 if c._has_service():
349 return True
350
Harald Weltec91085e2022-02-10 18:05:45 +0100351 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200352 """Add a child (DF/EF) to this DF.
353 Args:
354 child: The new DF/EF to be added
355 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
356 """
Harald Welteb2edd142021-01-08 23:29:35 +0100357 if not isinstance(child, CardFile):
358 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100359 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100360 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100361 if child.name in CardFile.RESERVED_NAMES:
362 raise ValueError("File name %s is a reserved name" % (child.name))
363 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100364 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100365 if child.fid in self.children:
366 if ignore_existing:
367 return
Harald Weltec91085e2022-02-10 18:05:45 +0100368 raise ValueError(
369 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100370 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100371 raise ValueError(
372 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100373 if self.lookup_file_by_name(child.name):
374 if ignore_existing:
375 return
Harald Weltec91085e2022-02-10 18:05:45 +0100376 raise ValueError(
377 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100378 self.children[child.fid] = child
379 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100380 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100381 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100382 if isinstance(child, CardDF):
383 for c in child.children.values():
384 self._add_file_services(c)
385 if isinstance(c, CardDF):
Harald Welted56f45d2022-07-16 11:46:59 +0200386 for gc in c.children.values():
387 if isinstance(gc, CardDF):
388 if gc._has_service():
389 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100390
Harald Weltec91085e2022-02-10 18:05:45 +0100391 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200392 """Add a list of child (DF/EF) to this DF
393
394 Args:
395 children: List of new DF/EFs to be added
396 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
397 """
Harald Welteb2edd142021-01-08 23:29:35 +0100398 for child in children:
399 self.add_file(child, ignore_existing)
400
Harald Weltec91085e2022-02-10 18:05:45 +0100401 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200402 """Return a dict of {'identifier': File} that is selectable from the current DF.
403
404 Args:
405 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
406 If not specified, all selectables will be returned.
407 Returns:
408 dict containing all selectable items. Key is identifier (string), value
409 a reference to a CardFile (or derived class) instance.
410 """
Harald Welteb2edd142021-01-08 23:29:35 +0100411 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100412 sels = super().get_selectables(flags)
413 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100414 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100415 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100416 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100417 return sels
418
Harald Weltec91085e2022-02-10 18:05:45 +0100419 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200420 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100421 if name == None:
422 return None
423 for i in self.children.values():
424 if i.name and i.name == name:
425 return i
426 return None
427
Harald Weltec91085e2022-02-10 18:05:45 +0100428 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200429 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100430 if sfid == None:
431 return None
432 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200433 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100434 return i
435 return None
436
Harald Weltec91085e2022-02-10 18:05:45 +0100437 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200438 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100439 if fid in self.children:
440 return self.children[fid]
441 return None
442
443
444class CardMF(CardDF):
445 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100446
Harald Welteb2edd142021-01-08 23:29:35 +0100447 def __init__(self, **kwargs):
448 # can be overridden; use setdefault
449 kwargs.setdefault('fid', '3f00')
450 kwargs.setdefault('name', 'MF')
451 kwargs.setdefault('desc', 'Master File (directory root)')
452 # cannot be overridden; use assignment
453 kwargs['parent'] = self
454 super().__init__(**kwargs)
455 self.applications = dict()
456
457 def __str__(self):
458 return "MF(%s)" % (self.fid)
459
Harald Weltec91085e2022-02-10 18:05:45 +0100460 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200461 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100462 if not isinstance(app, CardADF):
463 raise TypeError("Expected an ADF instance")
464 if app.aid in self.applications:
465 raise ValueError("AID %s already exists" % (app.aid))
466 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100467 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100468
469 def get_app_names(self):
470 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100471 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100472
Harald Weltec91085e2022-02-10 18:05:45 +0100473 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200474 """Return a dict of {'identifier': File} that is selectable from the current DF.
475
476 Args:
477 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
478 If not specified, all selectables will be returned.
479 Returns:
480 dict containing all selectable items. Key is identifier (string), value
481 a reference to a CardFile (or derived class) instance.
482 """
Philipp Maier786f7812021-02-25 16:48:10 +0100483 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100484 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100485 return sels
486
Harald Weltec91085e2022-02-10 18:05:45 +0100487 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100488 """Get applications by AID + name"""
489 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100490 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100491 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100492 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100493 sels.update(
494 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100495 return sels
496
Harald Weltec9752512022-02-11 16:31:15 +0100497 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200498 """Decode the response to a SELECT command.
499
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100500 This is the fall-back method which automatically defers to the standard decoding
501 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100502 performed. Specific derived classes (usually ADF) can overload this method to
503 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200504 """
Harald Welteb2edd142021-01-08 23:29:35 +0100505
Harald Weltec9752512022-02-11 16:31:15 +0100506 if not data_hex:
507 return data_hex
508
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100509 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100510
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100511 if profile:
512 return profile.decode_select_response(data_hex)
513 else:
514 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100515
Harald Weltec91085e2022-02-10 18:05:45 +0100516
Harald Welteb2edd142021-01-08 23:29:35 +0100517class CardADF(CardDF):
518 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100519
520 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100521 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200522 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200523 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100524 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200525 mf = self.get_mf()
526 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200527 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100528
529 def __str__(self):
Harald Welte4b003652022-07-16 11:55:07 +0200530 return "ADF(%s)" % (self.name if self.name else self.aid)
Harald Welteb2edd142021-01-08 23:29:35 +0100531
Harald Weltec91085e2022-02-10 18:05:45 +0100532 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100533 if self.name and prefer_name:
534 return self.name
535 else:
536 return self.aid
537
538
539class CardEF(CardFile):
540 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100541
Harald Welteb2edd142021-01-08 23:29:35 +0100542 def __init__(self, *, fid, **kwargs):
543 kwargs['fid'] = fid
544 super().__init__(**kwargs)
545
546 def __str__(self):
547 return "EF(%s)" % (super().__str__())
548
Harald Weltec91085e2022-02-10 18:05:45 +0100549 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200550 """Return a dict of {'identifier': File} that is selectable from the current DF.
551
552 Args:
553 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
554 If not specified, all selectables will be returned.
555 Returns:
556 dict containing all selectable items. Key is identifier (string), value
557 a reference to a CardFile (or derived class) instance.
558 """
Harald Weltec91085e2022-02-10 18:05:45 +0100559 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100560 sels = super().get_selectables(flags)
Harald Welted2c177b2022-07-24 11:35:53 +0200561 if flags == [] or 'FIDS' in flags:
562 sels.update({x.fid: x for x in self.parent.children.values() if x.fid and x != self})
563 if flags == [] or 'FNAMES' in flags:
564 sels.update({x.name: x for x in self.parent.children.values() if x.name and x != self})
Harald Welteb2edd142021-01-08 23:29:35 +0100565 return sels
566
567
568class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200569 """Transparent EF (Entry File) in the smart card filesystem.
570
571 A Transparent EF is a binary file with no formal structure. This is contrary to
572 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100573
574 @with_default_category('Transparent EF Commands')
575 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200576 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100577
Harald Welteb2edd142021-01-08 23:29:35 +0100578 def __init__(self):
579 super().__init__()
580
Harald Welteaefd0642022-02-25 15:26:37 +0100581 dec_hex_parser = argparse.ArgumentParser()
582 dec_hex_parser.add_argument('--oneline', action='store_true',
583 help='No JSON pretty-printing, dump as a single line')
584 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
585
586 @cmd2.with_argparser(dec_hex_parser)
587 def do_decode_hex(self, opts):
588 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200589 data = self._cmd.lchan.selected_file.decode_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100590 self._cmd.poutput_json(data, opts.oneline)
591
Harald Welteb2edd142021-01-08 23:29:35 +0100592 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100593 read_bin_parser.add_argument(
594 '--offset', type=int, default=0, help='Byte offset for start of read')
595 read_bin_parser.add_argument(
596 '--length', type=int, help='Number of bytes to read')
597
Harald Welteb2edd142021-01-08 23:29:35 +0100598 @cmd2.with_argparser(read_bin_parser)
599 def do_read_binary(self, opts):
600 """Read binary data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200601 (data, sw) = self._cmd.lchan.read_binary(opts.length, opts.offset)
Harald Welteb2edd142021-01-08 23:29:35 +0100602 self._cmd.poutput(data)
603
Harald Weltebcad86c2021-04-06 20:08:39 +0200604 read_bin_dec_parser = argparse.ArgumentParser()
605 read_bin_dec_parser.add_argument('--oneline', action='store_true',
606 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100607
Harald Weltebcad86c2021-04-06 20:08:39 +0200608 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100609 def do_read_binary_decoded(self, opts):
610 """Read + decode data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200611 (data, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200612 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100613
614 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100615 upd_bin_parser.add_argument(
616 '--offset', type=int, default=0, help='Byte offset for start of read')
617 upd_bin_parser.add_argument(
618 'data', help='Data bytes (hex format) to write')
619
Harald Welteb2edd142021-01-08 23:29:35 +0100620 @cmd2.with_argparser(upd_bin_parser)
621 def do_update_binary(self, opts):
622 """Update (Write) data of a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200623 (data, sw) = self._cmd.lchan.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100624 if data:
625 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100626
627 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100628 upd_bin_dec_parser.add_argument(
629 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200630 upd_bin_dec_parser.add_argument('--json-path', type=str,
631 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100632
Harald Welteb2edd142021-01-08 23:29:35 +0100633 @cmd2.with_argparser(upd_bin_dec_parser)
634 def do_update_binary_decoded(self, opts):
635 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200636 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200637 (data_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100638 js_path_modify(data_json, opts.json_path,
639 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200640 else:
641 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200642 (data, sw) = self._cmd.lchan.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100643 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200644 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100645
Harald Welte4145d3c2021-04-08 20:34:13 +0200646 def do_edit_binary_decoded(self, opts):
647 """Edit the JSON representation of the EF contents in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200648 (orig_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte4145d3c2021-04-08 20:34:13 +0200649 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
650 filename = '%s/file' % dirname
651 # write existing data as JSON to file
652 with open(filename, 'w') as text_file:
653 json.dump(orig_json, text_file, indent=4)
654 # run a text editor
Harald Weltee1268722023-06-24 07:57:08 +0200655 self._cmd.run_editor(filename)
Harald Welte4145d3c2021-04-08 20:34:13 +0200656 with open(filename, 'r') as text_file:
657 edited_json = json.load(text_file)
658 if edited_json == orig_json:
659 self._cmd.poutput("Data not modified, skipping write")
660 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200661 (data, sw) = self._cmd.lchan.update_binary_dec(edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200662 if data:
663 self._cmd.poutput_json(data)
664
Harald Weltec91085e2022-02-10 18:05:45 +0100665 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte13edf302022-07-21 15:19:23 +0200666 size: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200667 """
668 Args:
669 fid : File Identifier (4 hex digits)
670 sfid : Short File Identifier (2 hex digits, optional)
671 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200672 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200673 parent : Parent CardFile object within filesystem hierarchy
674 size : tuple of (minimum_size, recommended_size)
675 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100676 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200677 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200678 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100679 self.size = size
680 self.shell_commands = [self.ShellCommands()]
681
Harald Weltec91085e2022-02-10 18:05:45 +0100682 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200683 """Decode raw (binary) data into abstract representation.
684
685 A derived class would typically provide a _decode_bin() or _decode_hex() method
686 for implementing this specifically for the given file. This function checks which
687 of the method exists, add calls them (with conversion, as needed).
688
689 Args:
690 raw_bin_data : binary encoded data
691 Returns:
692 abstract_data; dict representing the decoded data
693 """
Harald Welteb2edd142021-01-08 23:29:35 +0100694 method = getattr(self, '_decode_bin', None)
695 if callable(method):
696 return method(raw_bin_data)
697 method = getattr(self, '_decode_hex', None)
698 if callable(method):
699 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200700 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200701 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200702 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100703 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100704 t.from_tlv(raw_bin_data)
705 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100706 return {'raw': raw_bin_data.hex()}
707
Harald Weltec91085e2022-02-10 18:05:45 +0100708 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200709 """Decode raw (hex string) data into abstract representation.
710
711 A derived class would typically provide a _decode_bin() or _decode_hex() method
712 for implementing this specifically for the given file. This function checks which
713 of the method exists, add calls them (with conversion, as needed).
714
715 Args:
716 raw_hex_data : hex-encoded data
717 Returns:
718 abstract_data; dict representing the decoded data
719 """
Harald Welteb2edd142021-01-08 23:29:35 +0100720 method = getattr(self, '_decode_hex', None)
721 if callable(method):
722 return method(raw_hex_data)
723 raw_bin_data = h2b(raw_hex_data)
724 method = getattr(self, '_decode_bin', None)
725 if callable(method):
726 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200727 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200728 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200729 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100730 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100731 t.from_tlv(raw_bin_data)
732 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100733 return {'raw': raw_bin_data.hex()}
734
Harald Weltec91085e2022-02-10 18:05:45 +0100735 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200736 """Encode abstract representation into raw (binary) data.
737
738 A derived class would typically provide an _encode_bin() or _encode_hex() method
739 for implementing this specifically for the given file. This function checks which
740 of the method exists, add calls them (with conversion, as needed).
741
742 Args:
743 abstract_data : dict representing the decoded data
744 Returns:
745 binary encoded data
746 """
Harald Welteb2edd142021-01-08 23:29:35 +0100747 method = getattr(self, '_encode_bin', None)
748 if callable(method):
749 return method(abstract_data)
750 method = getattr(self, '_encode_hex', None)
751 if callable(method):
752 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200753 if self._construct:
754 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200755 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100756 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100757 t.from_dict(abstract_data)
758 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100759 raise NotImplementedError(
760 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100761
Harald Weltec91085e2022-02-10 18:05:45 +0100762 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200763 """Encode abstract representation into raw (hex string) data.
764
765 A derived class would typically provide an _encode_bin() or _encode_hex() method
766 for implementing this specifically for the given file. This function checks which
767 of the method exists, add calls them (with conversion, as needed).
768
769 Args:
770 abstract_data : dict representing the decoded data
771 Returns:
772 hex string encoded data
773 """
Harald Welteb2edd142021-01-08 23:29:35 +0100774 method = getattr(self, '_encode_hex', None)
775 if callable(method):
776 return method(abstract_data)
777 method = getattr(self, '_encode_bin', None)
778 if callable(method):
779 raw_bin_data = method(abstract_data)
780 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200781 if self._construct:
782 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200783 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100784 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100785 t.from_dict(abstract_data)
786 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100787 raise NotImplementedError(
788 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100789
790
791class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200792 """Linear Fixed EF (Entry File) in the smart card filesystem.
793
794 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
795 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100796
797 @with_default_category('Linear Fixed EF Commands')
798 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200799 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100800
Harald Welte9170fbf2022-02-11 21:54:37 +0100801 def __init__(self, **kwargs):
802 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100803
Harald Welteaefd0642022-02-25 15:26:37 +0100804 dec_hex_parser = argparse.ArgumentParser()
805 dec_hex_parser.add_argument('--oneline', action='store_true',
806 help='No JSON pretty-printing, dump as a single line')
807 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
808
809 @cmd2.with_argparser(dec_hex_parser)
810 def do_decode_hex(self, opts):
811 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200812 data = self._cmd.lchan.selected_file.decode_record_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100813 self._cmd.poutput_json(data, opts.oneline)
814
Harald Welteb2edd142021-01-08 23:29:35 +0100815 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100816 read_rec_parser.add_argument(
817 'record_nr', type=int, help='Number of record to be read')
818 read_rec_parser.add_argument(
819 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
820
Harald Welteb2edd142021-01-08 23:29:35 +0100821 @cmd2.with_argparser(read_rec_parser)
822 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100823 """Read one or multiple records from a record-oriented EF"""
824 for r in range(opts.count):
825 recnr = opts.record_nr + r
Harald Weltea6c0f882022-07-17 14:23:17 +0200826 (data, sw) = self._cmd.lchan.read_record(recnr)
Philipp Maier41555732021-02-25 16:52:08 +0100827 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100828 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100829 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100830 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100831 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100832
833 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100834 read_rec_dec_parser.add_argument(
835 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200836 read_rec_dec_parser.add_argument('--oneline', action='store_true',
837 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100838
Harald Welteb2edd142021-01-08 23:29:35 +0100839 @cmd2.with_argparser(read_rec_dec_parser)
840 def do_read_record_decoded(self, opts):
841 """Read + decode a record from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200842 (data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200843 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100844
Harald Welte850b72a2021-04-07 09:33:03 +0200845 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100846
Harald Welte850b72a2021-04-07 09:33:03 +0200847 @cmd2.with_argparser(read_recs_parser)
848 def do_read_records(self, opts):
849 """Read all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200850 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200851 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200852 (data, sw) = self._cmd.lchan.read_record(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200853 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100854 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200855 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100856 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200857 self._cmd.poutput("%03d %s" % (recnr, recstr))
858
859 read_recs_dec_parser = argparse.ArgumentParser()
860 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100861 help='No JSON pretty-printing, dump as a single line')
862
Harald Welte850b72a2021-04-07 09:33:03 +0200863 @cmd2.with_argparser(read_recs_dec_parser)
864 def do_read_records_decoded(self, opts):
865 """Read + decode all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200866 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200867 # collect all results in list so they are rendered as JSON list when printing
868 data_list = []
869 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200870 (data, sw) = self._cmd.lchan.read_record_dec(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200871 data_list.append(data)
872 self._cmd.poutput_json(data_list, opts.oneline)
873
Harald Welteb2edd142021-01-08 23:29:35 +0100874 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100875 upd_rec_parser.add_argument(
876 'record_nr', type=int, help='Number of record to be read')
877 upd_rec_parser.add_argument(
878 'data', help='Data bytes (hex format) to write')
879
Harald Welteb2edd142021-01-08 23:29:35 +0100880 @cmd2.with_argparser(upd_rec_parser)
881 def do_update_record(self, opts):
882 """Update (write) data to a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200883 (data, sw) = self._cmd.lchan.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100884 if data:
885 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100886
887 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100888 upd_rec_dec_parser.add_argument(
889 'record_nr', type=int, help='Number of record to be read')
890 upd_rec_dec_parser.add_argument(
891 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200892 upd_rec_dec_parser.add_argument('--json-path', type=str,
893 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100894
Harald Welteb2edd142021-01-08 23:29:35 +0100895 @cmd2.with_argparser(upd_rec_dec_parser)
896 def do_update_record_decoded(self, opts):
897 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200898 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200899 (data_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100900 js_path_modify(data_json, opts.json_path,
901 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200902 else:
903 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200904 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100905 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100906 if data:
907 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100908
Harald Welte4145d3c2021-04-08 20:34:13 +0200909 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100910 edit_rec_dec_parser.add_argument(
911 'record_nr', type=int, help='Number of record to be edited')
912
Harald Welte4145d3c2021-04-08 20:34:13 +0200913 @cmd2.with_argparser(edit_rec_dec_parser)
914 def do_edit_record_decoded(self, opts):
915 """Edit the JSON representation of one record in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200916 (orig_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200917 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200918 filename = '%s/file' % dirname
919 # write existing data as JSON to file
920 with open(filename, 'w') as text_file:
921 json.dump(orig_json, text_file, indent=4)
922 # run a text editor
Harald Weltee1268722023-06-24 07:57:08 +0200923 self._cmd.run_editor(filename)
Harald Welte4145d3c2021-04-08 20:34:13 +0200924 with open(filename, 'r') as text_file:
925 edited_json = json.load(text_file)
926 if edited_json == orig_json:
927 self._cmd.poutput("Data not modified, skipping write")
928 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200929 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100930 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200931 if data:
932 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200933
Harald Weltec91085e2022-02-10 18:05:45 +0100934 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte99e4cc02022-07-21 15:25:47 +0200935 parent: Optional[CardDF] = None, rec_len: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200936 """
937 Args:
938 fid : File Identifier (4 hex digits)
939 sfid : Short File Identifier (2 hex digits, optional)
940 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200941 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200942 parent : Parent CardFile object within filesystem hierarchy
Harald Welte99e4cc02022-07-21 15:25:47 +0200943 rec_len : Tuple of (minimum_length, recommended_length)
Harald Welteee3501f2021-04-02 13:00:18 +0200944 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100945 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100946 self.rec_len = rec_len
947 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200948 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200949 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100950
Harald Weltecaa94b52023-01-31 16:43:34 +0100951 def decode_record_hex(self, raw_hex_data: str, record_nr: int = 1) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200952 """Decode raw (hex string) data into abstract representation.
953
954 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
955 method for implementing this specifically for the given file. This function checks which
956 of the method exists, add calls them (with conversion, as needed).
957
958 Args:
959 raw_hex_data : hex-encoded data
Harald Weltef6b37af2023-01-24 15:42:26 +0100960 record_nr : record number (1 for first record, ...)
Harald Welteee3501f2021-04-02 13:00:18 +0200961 Returns:
962 abstract_data; dict representing the decoded data
963 """
Harald Welteb2edd142021-01-08 23:29:35 +0100964 method = getattr(self, '_decode_record_hex', None)
965 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +0100966 return method(raw_hex_data, record_nr=record_nr)
Harald Welteb2edd142021-01-08 23:29:35 +0100967 raw_bin_data = h2b(raw_hex_data)
968 method = getattr(self, '_decode_record_bin', None)
969 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +0100970 return method(raw_bin_data, record_nr=record_nr)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200971 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200972 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200973 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100974 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100975 t.from_tlv(raw_bin_data)
976 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100977 return {'raw': raw_bin_data.hex()}
978
Harald Weltef6b37af2023-01-24 15:42:26 +0100979 def decode_record_bin(self, raw_bin_data: bytearray, record_nr: int) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200980 """Decode raw (binary) data into abstract representation.
981
982 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
983 method for implementing this specifically for the given file. This function checks which
984 of the method exists, add calls them (with conversion, as needed).
985
986 Args:
987 raw_bin_data : binary encoded data
Harald Weltef6b37af2023-01-24 15:42:26 +0100988 record_nr : record number (1 for first record, ...)
Harald Welteee3501f2021-04-02 13:00:18 +0200989 Returns:
990 abstract_data; dict representing the decoded data
991 """
Harald Welteb2edd142021-01-08 23:29:35 +0100992 method = getattr(self, '_decode_record_bin', None)
993 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +0100994 return method(raw_bin_data, record_nr=record_nr)
Harald Welteb2edd142021-01-08 23:29:35 +0100995 raw_hex_data = b2h(raw_bin_data)
996 method = getattr(self, '_decode_record_hex', None)
997 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +0100998 return method(raw_hex_data, record_nr=record_nr)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200999 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001000 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001001 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001002 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001003 t.from_tlv(raw_bin_data)
1004 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001005 return {'raw': raw_hex_data}
1006
Harald Weltef6b37af2023-01-24 15:42:26 +01001007 def encode_record_hex(self, abstract_data: dict, record_nr: int) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001008 """Encode abstract representation into raw (hex string) data.
1009
1010 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1011 method for implementing this specifically for the given file. This function checks which
1012 of the method exists, add calls them (with conversion, as needed).
1013
1014 Args:
1015 abstract_data : dict representing the decoded data
Harald Weltef6b37af2023-01-24 15:42:26 +01001016 record_nr : record number (1 for first record, ...)
Harald Welteee3501f2021-04-02 13:00:18 +02001017 Returns:
1018 hex string encoded data
1019 """
Harald Welteb2edd142021-01-08 23:29:35 +01001020 method = getattr(self, '_encode_record_hex', None)
1021 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +01001022 return method(abstract_data, record_nr=record_nr)
Harald Welteb2edd142021-01-08 23:29:35 +01001023 method = getattr(self, '_encode_record_bin', None)
1024 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +01001025 raw_bin_data = method(abstract_data, record_nr=record_nr)
Harald Welte1e456572021-04-02 17:16:30 +02001026 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001027 if self._construct:
1028 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001029 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001030 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001031 t.from_dict(abstract_data)
1032 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001033 raise NotImplementedError(
1034 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001035
Harald Weltef6b37af2023-01-24 15:42:26 +01001036 def encode_record_bin(self, abstract_data: dict, record_nr : int) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001037 """Encode abstract representation into raw (binary) data.
1038
1039 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1040 method for implementing this specifically for the given file. This function checks which
1041 of the method exists, add calls them (with conversion, as needed).
1042
1043 Args:
1044 abstract_data : dict representing the decoded data
Harald Weltef6b37af2023-01-24 15:42:26 +01001045 record_nr : record number (1 for first record, ...)
Harald Welteee3501f2021-04-02 13:00:18 +02001046 Returns:
1047 binary encoded data
1048 """
Harald Welteb2edd142021-01-08 23:29:35 +01001049 method = getattr(self, '_encode_record_bin', None)
1050 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +01001051 return method(abstract_data, record_nr=record_nr)
Harald Welteb2edd142021-01-08 23:29:35 +01001052 method = getattr(self, '_encode_record_hex', None)
1053 if callable(method):
Harald Weltef6b37af2023-01-24 15:42:26 +01001054 return h2b(method(abstract_data, record_nr=record_nr))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001055 if self._construct:
1056 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +02001057 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001058 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001059 t.from_dict(abstract_data)
1060 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001061 raise NotImplementedError(
1062 "%s encoder not yet implemented. Patches welcome." % self)
1063
Harald Welteb2edd142021-01-08 23:29:35 +01001064
1065class CyclicEF(LinFixedEF):
1066 """Cyclic EF (Entry File) in the smart card filesystem"""
1067 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +01001068
1069 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte99e4cc02022-07-21 15:25:47 +02001070 rec_len: Size = (1, None), **kwargs):
Harald Welte9170fbf2022-02-11 21:54:37 +01001071 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +01001072
Harald Welteb2edd142021-01-08 23:29:35 +01001073
1074class TransRecEF(TransparentEF):
1075 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +02001076
Harald Welteb2edd142021-01-08 23:29:35 +01001077 These are the real odd-balls and mostly look like mistakes in the specification:
1078 Specified as 'transparent' EF, but actually containing several fixed-length records
1079 inside.
1080 We add a special class for those, so the user only has to provide encoder/decoder functions
1081 for a record, while this class takes care of split / merge of records.
1082 """
Harald Weltec91085e2022-02-10 18:05:45 +01001083
1084 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte13edf302022-07-21 15:19:23 +02001085 parent: Optional[CardDF] = None, size: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +02001086 """
1087 Args:
1088 fid : File Identifier (4 hex digits)
1089 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +02001090 name : Brief name of the file, like EF_ICCID
1091 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +02001092 parent : Parent CardFile object within filesystem hierarchy
1093 rec_len : Length of the fixed-length records within transparent EF
1094 size : tuple of (minimum_size, recommended_size)
1095 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001096 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +01001097 self.rec_len = rec_len
1098
Harald Weltec91085e2022-02-10 18:05:45 +01001099 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001100 """Decode raw (hex string) data into abstract representation.
1101
1102 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1103 method for implementing this specifically for the given file. This function checks which
1104 of the method exists, add calls them (with conversion, as needed).
1105
1106 Args:
1107 raw_hex_data : hex-encoded data
1108 Returns:
1109 abstract_data; dict representing the decoded data
1110 """
Harald Welteb2edd142021-01-08 23:29:35 +01001111 method = getattr(self, '_decode_record_hex', None)
1112 if callable(method):
1113 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001114 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001115 method = getattr(self, '_decode_record_bin', None)
1116 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001117 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001118 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001119 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001120 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001121 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001122 t.from_tlv(raw_bin_data)
1123 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001124 return {'raw': raw_hex_data}
1125
Harald Weltec91085e2022-02-10 18:05:45 +01001126 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001127 """Decode raw (binary) data into abstract representation.
1128
1129 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1130 method for implementing this specifically for the given file. This function checks which
1131 of the method exists, add calls them (with conversion, as needed).
1132
1133 Args:
1134 raw_bin_data : binary encoded data
1135 Returns:
1136 abstract_data; dict representing the decoded data
1137 """
Harald Welteb2edd142021-01-08 23:29:35 +01001138 method = getattr(self, '_decode_record_bin', None)
1139 if callable(method):
1140 return method(raw_bin_data)
1141 raw_hex_data = b2h(raw_bin_data)
1142 method = getattr(self, '_decode_record_hex', None)
1143 if callable(method):
1144 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001145 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001146 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001147 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001148 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001149 t.from_tlv(raw_bin_data)
1150 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001151 return {'raw': raw_hex_data}
1152
Harald Weltec91085e2022-02-10 18:05:45 +01001153 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001154 """Encode abstract representation into raw (hex string) data.
1155
1156 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1157 method for implementing this specifically for the given file. This function checks which
1158 of the method exists, add calls them (with conversion, as needed).
1159
1160 Args:
1161 abstract_data : dict representing the decoded data
1162 Returns:
1163 hex string encoded data
1164 """
Harald Welteb2edd142021-01-08 23:29:35 +01001165 method = getattr(self, '_encode_record_hex', None)
1166 if callable(method):
1167 return method(abstract_data)
1168 method = getattr(self, '_encode_record_bin', None)
1169 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001170 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001171 if self._construct:
1172 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001173 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001174 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001175 t.from_dict(abstract_data)
1176 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001177 raise NotImplementedError(
1178 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001179
Harald Weltec91085e2022-02-10 18:05:45 +01001180 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001181 """Encode abstract representation into raw (binary) data.
1182
1183 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1184 method for implementing this specifically for the given file. This function checks which
1185 of the method exists, add calls them (with conversion, as needed).
1186
1187 Args:
1188 abstract_data : dict representing the decoded data
1189 Returns:
1190 binary encoded data
1191 """
Harald Welteb2edd142021-01-08 23:29:35 +01001192 method = getattr(self, '_encode_record_bin', None)
1193 if callable(method):
1194 return method(abstract_data)
1195 method = getattr(self, '_encode_record_hex', None)
1196 if callable(method):
1197 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001198 if self._construct:
1199 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001200 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001201 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001202 t.from_dict(abstract_data)
1203 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001204 raise NotImplementedError(
1205 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001206
Harald Weltec91085e2022-02-10 18:05:45 +01001207 def _decode_bin(self, raw_bin_data: bytearray):
1208 chunks = [raw_bin_data[i:i+self.rec_len]
1209 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001210 return [self.decode_record_bin(x) for x in chunks]
1211
Harald Welteee3501f2021-04-02 13:00:18 +02001212 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001213 chunks = [self.encode_record_bin(x) for x in abstract_data]
1214 # FIXME: pad to file size
1215 return b''.join(chunks)
1216
1217
Harald Welte917d98c2021-04-21 11:51:25 +02001218class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001219 """BER-TLV EF (Entry File) in the smart card filesystem.
1220 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001221
Harald Welte27881622021-04-21 11:16:31 +02001222 NOTE: We currently don't really support those, this class is simply a wrapper
1223 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1224 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001225
Harald Welte917d98c2021-04-21 11:51:25 +02001226 @with_default_category('BER-TLV EF Commands')
1227 class ShellCommands(CommandSet):
1228 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001229
Harald Welte917d98c2021-04-21 11:51:25 +02001230 def __init__(self):
1231 super().__init__()
1232
1233 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001234 retrieve_data_parser.add_argument(
1235 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1236
Harald Welte917d98c2021-04-21 11:51:25 +02001237 @cmd2.with_argparser(retrieve_data_parser)
1238 def do_retrieve_data(self, opts):
1239 """Retrieve (Read) data from a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001240 (data, sw) = self._cmd.lchan.retrieve_data(opts.tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001241 self._cmd.poutput(data)
1242
1243 def do_retrieve_tags(self, opts):
1244 """List tags available in a given BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001245 tags = self._cmd.lchan.retrieve_tags()
Harald Welte917d98c2021-04-21 11:51:25 +02001246 self._cmd.poutput(tags)
1247
1248 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001249 set_data_parser.add_argument(
1250 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1251 set_data_parser.add_argument(
1252 'data', help='Data bytes (hex format) to write')
1253
Harald Welte917d98c2021-04-21 11:51:25 +02001254 @cmd2.with_argparser(set_data_parser)
1255 def do_set_data(self, opts):
1256 """Set (Write) data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001257 (data, sw) = self._cmd.lchan.set_data(opts.tag, opts.data)
Harald Welte917d98c2021-04-21 11:51:25 +02001258 if data:
1259 self._cmd.poutput(data)
1260
1261 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001262 del_data_parser.add_argument(
1263 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1264
Harald Welte917d98c2021-04-21 11:51:25 +02001265 @cmd2.with_argparser(del_data_parser)
1266 def do_delete_data(self, opts):
1267 """Delete data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001268 (data, sw) = self._cmd.lchan.set_data(opts.tag, None)
Harald Welte917d98c2021-04-21 11:51:25 +02001269 if data:
1270 self._cmd.poutput(data)
1271
Harald Weltec91085e2022-02-10 18:05:45 +01001272 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte13edf302022-07-21 15:19:23 +02001273 size: Size = (1, None), **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001274 """
1275 Args:
1276 fid : File Identifier (4 hex digits)
1277 sfid : Short File Identifier (2 hex digits, optional)
1278 name : Brief name of the file, lik EF_ICCID
1279 desc : Description of the file
1280 parent : Parent CardFile object within filesystem hierarchy
1281 size : tuple of (minimum_size, recommended_size)
1282 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001283 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001284 self._construct = None
1285 self.size = size
1286 self.shell_commands = [self.ShellCommands()]
1287
Harald Welteb2edd142021-01-08 23:29:35 +01001288
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001289class RuntimeState:
Harald Welteb2edd142021-01-08 23:29:35 +01001290 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001291
1292 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001293 """
1294 Args:
1295 card : pysim.cards.Card instance
1296 profile : CardProfile instance
1297 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001298 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001299 self.card = card
Harald Welteb2edd142021-01-08 23:29:35 +01001300 self.profile = profile
Harald Weltea6c0f882022-07-17 14:23:17 +02001301 self.lchan = {}
1302 # the basic logical channel always exists
1303 self.lchan[0] = RuntimeLchan(0, self)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001304
1305 # make sure the class and selection control bytes, which are specified
1306 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001307 self.card.set_apdu_parameter(
1308 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001309
Harald Welte5ce35242021-04-02 20:27:05 +02001310 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001311 apps = self._match_applications()
1312 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001313 if a.adf:
1314 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001315 for f in self.profile.files_in_mf:
1316 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001317 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001318
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001319 # make sure that when the runtime state is created, the card is also
1320 # in a defined state.
1321 self.reset()
1322
Philipp Maier1e896f32021-03-10 17:02:53 +01001323 def _match_applications(self):
1324 """match the applications from the profile with applications on the card"""
1325 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001326
1327 # When the profile does not feature any applications, then we are done already
1328 if not apps_profile:
1329 return []
1330
1331 # Read AIDs from card and match them against the applications defined by the
1332 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001333 aids_card = self.card.read_aids()
1334 apps_taken = []
1335 if aids_card:
1336 aids_taken = []
1337 print("AIDs on card:")
1338 for a in aids_card:
1339 for f in apps_profile:
1340 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001341 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001342 aids_taken.append(a)
1343 apps_taken.append(f)
1344 aids_unknown = set(aids_card) - set(aids_taken)
1345 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001346 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001347 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001348 print("warning: EF.DIR seems to be empty!")
1349
1350 # Some card applications may not be registered in EF.DIR, we will actively
1351 # probe for those applications
1352 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001353 try:
1354 data, sw = self.card.select_adf_by_aid(f.aid)
1355 if sw == "9000":
1356 print(" %s: %s" % (f.name, f.aid))
1357 apps_taken.append(f)
Tobias Engeld70ac222023-05-29 21:20:59 +02001358 except (SwMatchError, ProtocolError):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001359 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001360 return apps_taken
1361
Harald Weltea6c0f882022-07-17 14:23:17 +02001362 def reset(self, cmd_app=None) -> Hexstr:
1363 """Perform physical card reset and obtain ATR.
1364 Args:
1365 cmd_app : Command Application State (for unregistering old file commands)
1366 """
1367 # delete all lchan != 0 (basic lchan)
1368 for lchan_nr in self.lchan.keys():
1369 if lchan_nr == 0:
1370 continue
1371 del self.lchan[lchan_nr]
1372 atr = i2h(self.card.reset())
1373 # select MF to reset internal state and to verify card really works
1374 self.lchan[0].select('MF', cmd_app)
1375 self.lchan[0].selected_adf = None
1376 return atr
1377
1378 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1379 """Add a logical channel to the runtime state. You shouldn't call this
1380 directly but always go through RuntimeLchan.add_lchan()."""
1381 if lchan_nr in self.lchan.keys():
1382 raise ValueError('Cannot create already-existing lchan %d' % lchan_nr)
1383 self.lchan[lchan_nr] = RuntimeLchan(lchan_nr, self)
1384 return self.lchan[lchan_nr]
1385
1386 def del_lchan(self, lchan_nr: int):
1387 if lchan_nr in self.lchan.keys():
1388 del self.lchan[lchan_nr]
1389 return True
1390 else:
1391 return False
1392
1393 def get_lchan_by_cla(self, cla) -> Optional['RuntimeLchan']:
1394 lchan_nr = lchan_nr_from_cla(cla)
1395 if lchan_nr in self.lchan.keys():
1396 return self.lchan[lchan_nr]
1397 else:
1398 return None
1399
1400
1401class RuntimeLchan:
1402 """Represent the runtime state of a logical channel with a card."""
1403
1404 def __init__(self, lchan_nr: int, rs: RuntimeState):
1405 self.lchan_nr = lchan_nr
1406 self.rs = rs
1407 self.selected_file = self.rs.mf
1408 self.selected_adf = None
1409 self.selected_file_fcp = None
1410 self.selected_file_fcp_hex = None
1411
1412 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1413 """Add a new logical channel from the current logical channel. Just affects
1414 internal state, doesn't actually open a channel with the UICC."""
1415 new_lchan = self.rs.add_lchan(lchan_nr)
1416 # See TS 102 221 Table 8.3
1417 if self.lchan_nr != 0:
1418 new_lchan.selected_file = self.get_cwd()
1419 new_lchan.selected_adf = self.selected_adf
1420 return new_lchan
1421
Harald Welte747a9782022-02-13 17:52:28 +01001422 def selected_file_descriptor_byte(self) -> dict:
1423 return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
1424
1425 def selected_file_shareable(self) -> bool:
1426 return self.selected_file_descriptor_byte()['shareable']
1427
1428 def selected_file_structure(self) -> str:
1429 return self.selected_file_descriptor_byte()['structure']
1430
1431 def selected_file_type(self) -> str:
1432 return self.selected_file_descriptor_byte()['file_type']
1433
1434 def selected_file_num_of_rec(self) -> Optional[int]:
1435 return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
1436
Harald Welteee3501f2021-04-02 13:00:18 +02001437 def get_cwd(self) -> CardDF:
1438 """Obtain the current working directory.
1439
1440 Returns:
1441 CardDF instance
1442 """
Harald Welteb2edd142021-01-08 23:29:35 +01001443 if isinstance(self.selected_file, CardDF):
1444 return self.selected_file
1445 else:
1446 return self.selected_file.parent
1447
Harald Welte5ce35242021-04-02 20:27:05 +02001448 def get_application_df(self) -> Optional[CardADF]:
1449 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001450
1451 Returns:
1452 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001453 # iterate upwards from selected file; check if any is an ADF
1454 node = self.selected_file
1455 while node.parent != node:
1456 if isinstance(node, CardADF):
1457 return node
1458 node = node.parent
1459 return None
1460
Harald Weltec91085e2022-02-10 18:05:45 +01001461 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001462 """Interpret a given status word relative to the currently selected application
1463 or the underlying card profile.
1464
1465 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001466 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001467
1468 Returns:
1469 Tuple of two strings
1470 """
Harald Welte86fbd392021-04-02 22:13:09 +02001471 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001472 adf = self.get_application_df()
1473 if adf:
1474 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001475 # The application either comes with its own interpret_sw
1476 # method or we will use the interpret_sw method from the
1477 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001478 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001479 res = app.interpret_sw(sw)
Harald Weltea6c0f882022-07-17 14:23:17 +02001480 return res or self.rs.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001481
Harald Weltec91085e2022-02-10 18:05:45 +01001482 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001483 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001484 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001485 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001486 raise ValueError(
1487 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001488
1489 try:
Harald Weltea6c0f882022-07-17 14:23:17 +02001490 (data, sw) = self.rs.card._scc.select_file(fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001491 except SwMatchError as swm:
1492 k = self.interpret_sw(swm.sw_actual)
1493 if not k:
1494 raise(swm)
1495 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1496
1497 select_resp = self.selected_file.decode_select_response(data)
Harald Welte747a9782022-02-13 17:52:28 +01001498 if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001499 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1500 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001501 else:
Harald Welte747a9782022-02-13 17:52:28 +01001502 if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001503 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1504 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001505 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001506 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1507 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001508
1509 self.selected_file.add_files([f])
1510 self.selected_file = f
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001511 return select_resp, data
Philipp Maier63f572d2021-03-09 22:42:47 +01001512
Harald Welteaceb2a52022-02-12 21:41:59 +01001513 def _select_pre(self, cmd_app):
1514 # unregister commands of old file
1515 if cmd_app and self.selected_file.shell_commands:
1516 for c in self.selected_file.shell_commands:
1517 cmd_app.unregister_command_set(c)
1518
1519 def _select_post(self, cmd_app):
1520 # register commands of new file
1521 if cmd_app and self.selected_file.shell_commands:
1522 for c in self.selected_file.shell_commands:
1523 cmd_app.register_command_set(c)
1524
1525 def select_file(self, file: CardFile, cmd_app=None):
1526 """Select a file (EF, DF, ADF, MF, ...).
1527
1528 Args:
1529 file : CardFile [or derived class] instance
1530 cmd_app : Command Application State (for unregistering old file commands)
1531 """
1532 # we need to find a path from our self.selected_file to the destination
1533 inter_path = self.selected_file.build_select_path_to(file)
1534 if not inter_path:
1535 raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
1536
1537 self._select_pre(cmd_app)
1538
1539 for p in inter_path:
1540 try:
1541 if isinstance(p, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001542 (data, sw) = self.rs.card.select_adf_by_aid(p.aid)
Harald Welte46a7a3f2022-07-16 11:47:47 +02001543 self.selected_adf = p
Harald Welteaceb2a52022-02-12 21:41:59 +01001544 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001545 (data, sw) = self.rs.card._scc.select_file(p.fid)
Harald Welteaceb2a52022-02-12 21:41:59 +01001546 self.selected_file = p
1547 except SwMatchError as swm:
1548 self._select_post(cmd_app)
1549 raise(swm)
1550
1551 self._select_post(cmd_app)
1552
Harald Weltec91085e2022-02-10 18:05:45 +01001553 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001554 """Select a file (EF, DF, ADF, MF, ...).
1555
1556 Args:
1557 name : Name of file to select
1558 cmd_app : Command Application State (for unregistering old file commands)
1559 """
Harald Welteee670bc2022-02-13 15:10:15 +01001560 # handling of entire paths with multiple directories/elements
1561 if '/' in name:
1562 prev_sel_file = self.selected_file
1563 pathlist = name.split('/')
1564 # treat /DF.GSM/foo like MF/DF.GSM/foo
1565 if pathlist[0] == '':
1566 pathlist[0] = 'MF'
1567 try:
1568 for p in pathlist:
1569 self.select(p, cmd_app)
1570 return
1571 except Exception as e:
1572 # if any intermediate step fails, go back to where we were
1573 self.select_file(prev_sel_file, cmd_app)
1574 raise e
1575
Harald Welteb2edd142021-01-08 23:29:35 +01001576 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001577 if is_hex(name):
1578 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001579
Harald Welteaceb2a52022-02-12 21:41:59 +01001580 self._select_pre(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001581
Harald Welteb2edd142021-01-08 23:29:35 +01001582 if name in sels:
1583 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001584 try:
1585 if isinstance(f, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001586 (data, sw) = self.rs.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001587 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001588 (data, sw) = self.rs.card._scc.select_file(f.fid)
Harald Welteb2edd142021-01-08 23:29:35 +01001589 self.selected_file = f
1590 except SwMatchError as swm:
1591 k = self.interpret_sw(swm.sw_actual)
1592 if not k:
1593 raise(swm)
1594 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001595 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001596 else:
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001597 (select_resp, data) = self.probe_file(name, cmd_app)
1598
Harald Welte2bb17f32022-02-15 15:41:55 +01001599 # store the raw + decoded FCP for later reference
1600 self.selected_file_fcp_hex = data
Harald Welte850b72a2021-04-07 09:33:03 +02001601 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001602
Harald Welteaceb2a52022-02-12 21:41:59 +01001603 self._select_post(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001604 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001605
Harald Welte34b05d32021-05-25 22:03:13 +02001606 def status(self):
1607 """Request STATUS (current selected file FCP) from card."""
Harald Weltea6c0f882022-07-17 14:23:17 +02001608 (data, sw) = self.rs.card._scc.status()
Harald Welte34b05d32021-05-25 22:03:13 +02001609 return self.selected_file.decode_select_response(data)
1610
Harald Welte3c9b7842021-10-19 21:44:24 +02001611 def get_file_for_selectable(self, name: str):
1612 sels = self.selected_file.get_selectables()
1613 return sels[name]
1614
Harald Weltec91085e2022-02-10 18:05:45 +01001615 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001616 """Request ACTIVATE FILE of specified file."""
1617 sels = self.selected_file.get_selectables()
1618 f = sels[name]
Harald Weltea6c0f882022-07-17 14:23:17 +02001619 data, sw = self.rs.card._scc.activate_file(f.fid)
Harald Welte485692b2021-05-25 22:21:44 +02001620 return data, sw
1621
Harald Weltec91085e2022-02-10 18:05:45 +01001622 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001623 """Read [part of] a transparent EF binary data.
1624
1625 Args:
1626 length : Amount of data to read (None: as much as possible)
1627 offset : Offset into the file from which to read 'length' bytes
1628 Returns:
1629 binary data read from the file
1630 """
Harald Welteb2edd142021-01-08 23:29:35 +01001631 if not isinstance(self.selected_file, TransparentEF):
1632 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001633 return self.rs.card._scc.read_binary(self.selected_file.fid, length, offset)
Harald Welteb2edd142021-01-08 23:29:35 +01001634
Harald Welte2d4a64b2021-04-03 09:01:24 +02001635 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001636 """Read [part of] a transparent EF binary data and decode it.
1637
1638 Args:
1639 length : Amount of data to read (None: as much as possible)
1640 offset : Offset into the file from which to read 'length' bytes
1641 Returns:
1642 abstract decode data read from the file
1643 """
Harald Welteb2edd142021-01-08 23:29:35 +01001644 (data, sw) = self.read_binary()
1645 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001646 return (dec_data, sw)
1647
Harald Weltec91085e2022-02-10 18:05:45 +01001648 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001649 """Update transparent EF binary data.
1650
1651 Args:
1652 data_hex : hex string of data to be written
1653 offset : Offset into the file from which to write 'data_hex'
1654 """
Harald Welteb2edd142021-01-08 23:29:35 +01001655 if not isinstance(self.selected_file, TransparentEF):
1656 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001657 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 +01001658
Harald Weltec91085e2022-02-10 18:05:45 +01001659 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001660 """Update transparent EF from abstract data. Encodes the data to binary and
1661 then updates the EF with it.
1662
1663 Args:
1664 data : abstract data which is to be encoded and written
1665 """
Harald Welteb2edd142021-01-08 23:29:35 +01001666 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001667 return self.update_binary(data_hex)
1668
Harald Weltec91085e2022-02-10 18:05:45 +01001669 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001670 """Read a record as binary data.
1671
1672 Args:
1673 rec_nr : Record number to read
1674 Returns:
1675 hex string of binary data contained in record
1676 """
Harald Welteb2edd142021-01-08 23:29:35 +01001677 if not isinstance(self.selected_file, LinFixedEF):
1678 raise TypeError("Only works with Linear Fixed EF")
1679 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001680 return self.rs.card._scc.read_record(self.selected_file.fid, rec_nr)
Harald Welteb2edd142021-01-08 23:29:35 +01001681
Harald Weltec91085e2022-02-10 18:05:45 +01001682 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001683 """Read a record and decode it to abstract data.
1684
1685 Args:
1686 rec_nr : Record number to read
1687 Returns:
1688 abstract data contained in record
1689 """
Harald Welteb2edd142021-01-08 23:29:35 +01001690 (data, sw) = self.read_record(rec_nr)
Harald Weltef6b37af2023-01-24 15:42:26 +01001691 return (self.selected_file.decode_record_hex(data, rec_nr), sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001692
Harald Weltec91085e2022-02-10 18:05:45 +01001693 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001694 """Update a record with given binary data
1695
1696 Args:
1697 rec_nr : Record number to read
1698 data_hex : Hex string binary data to be written
1699 """
Harald Welteb2edd142021-01-08 23:29:35 +01001700 if not isinstance(self.selected_file, LinFixedEF):
1701 raise TypeError("Only works with Linear Fixed EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001702 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 +01001703
Harald Weltec91085e2022-02-10 18:05:45 +01001704 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001705 """Update a record with given abstract data. Will encode abstract to binary data
1706 and then write it to the given record on the card.
1707
1708 Args:
1709 rec_nr : Record number to read
1710 data_hex : Abstract data to be written
1711 """
Harald Weltef6b37af2023-01-24 15:42:26 +01001712 data_hex = self.selected_file.encode_record_hex(data, rec_nr)
Harald Welte1e456572021-04-02 17:16:30 +02001713 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001714
Harald Weltec91085e2022-02-10 18:05:45 +01001715 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001716 """Read a DO/TLV as binary data.
1717
1718 Args:
1719 tag : Tag of TLV/DO to read
1720 Returns:
1721 hex string of full BER-TLV DO including Tag and Length
1722 """
1723 if not isinstance(self.selected_file, BerTlvEF):
1724 raise TypeError("Only works with BER-TLV EF")
1725 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001726 return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001727
1728 def retrieve_tags(self):
1729 """Retrieve tags available on BER-TLV EF.
1730
1731 Returns:
1732 list of integer tags contained in EF
1733 """
1734 if not isinstance(self.selected_file, BerTlvEF):
1735 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001736 data, sw = self.rs.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001737 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001738 return list(value)
1739
Harald Weltec91085e2022-02-10 18:05:45 +01001740 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001741 """Update a TLV/DO with given binary data
1742
1743 Args:
1744 tag : Tag of TLV/DO to be written
1745 data_hex : Hex string binary data to be written (value portion)
1746 """
1747 if not isinstance(self.selected_file, BerTlvEF):
1748 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001749 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 +02001750
Philipp Maier5d698e52021-09-16 13:18:01 +02001751 def unregister_cmds(self, cmd_app=None):
1752 """Unregister all file specific commands."""
1753 if cmd_app and self.selected_file.shell_commands:
1754 for c in self.selected_file.shell_commands:
1755 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001756
Harald Welteb2edd142021-01-08 23:29:35 +01001757
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001758class FileData:
Harald Welteb2edd142021-01-08 23:29:35 +01001759 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001760
Harald Welteb2edd142021-01-08 23:29:35 +01001761 def __init__(self, fdesc):
1762 self.desc = fdesc
1763 self.fcp = None
1764
1765
Harald Weltec91085e2022-02-10 18:05:45 +01001766def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001767 """Interpret a given status word.
1768
1769 Args:
1770 sw_data : Hierarchical dict of status word matches
1771 sw : status word to match (string of 4 hex digits)
1772 Returns:
1773 tuple of two strings (class_string, description)
1774 """
Harald Welteb2edd142021-01-08 23:29:35 +01001775 for class_str, swdict in sw_data.items():
1776 # first try direct match
1777 if sw in swdict:
1778 return (class_str, swdict[sw])
1779 # next try wildcard matches
1780 for pattern, descr in swdict.items():
1781 if sw_match(sw, pattern):
1782 return (class_str, descr)
1783 return None
1784
Harald Weltec91085e2022-02-10 18:05:45 +01001785
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001786class CardApplication:
Harald Welteb2edd142021-01-08 23:29:35 +01001787 """A card application is represented by an ADF (with contained hierarchy) and optionally
1788 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001789
1790 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001791 """
1792 Args:
1793 adf : ADF name
1794 sw : Dict of status word conversions
1795 """
Harald Welteb2edd142021-01-08 23:29:35 +01001796 self.name = name
1797 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001798 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001799 # back-reference from ADF to Applicaiton
1800 if self.adf:
1801 self.aid = aid or self.adf.aid
1802 self.adf.application = self
1803 else:
1804 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001805
1806 def __str__(self):
1807 return "APP(%s)" % (self.name)
1808
1809 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001810 """Interpret a given status word within the application.
1811
1812 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001813 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001814
1815 Returns:
1816 Tuple of two strings
1817 """
Harald Welteb2edd142021-01-08 23:29:35 +01001818 return interpret_sw(self.sw, sw)
1819
Harald Weltef44256c2021-10-14 15:53:39 +02001820
1821class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001822 """A specific card model, typically having some additional vendor-specific files. All
1823 you need to do is to define a sub-class with a list of ATRs or an overridden match
1824 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001825 _atrs = []
1826
1827 @classmethod
1828 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001829 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001830 """Add model specific files to given RuntimeState."""
1831
1832 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001833 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001834 """Test if given card matches this model."""
1835 card_atr = scc.get_atr()
1836 for atr in cls._atrs:
1837 atr_bin = toBytes(atr)
1838 if atr_bin == card_atr:
1839 print("Detected CardModel:", cls.__name__)
1840 return True
1841 return False
1842
1843 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001844 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001845 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1846 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1847 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001848 for m in CardModel.__subclasses__():
1849 if m.match(scc):
1850 m.add_files(rs)