blob: 886a48ddbfc9adbd0e9f208378129664c78135ba [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'."""
152 cur_fqpath = self.fully_qualified_path_fobj()
153 target_fqpath = target.fully_qualified_path_fobj()
154 inter_path = []
155 cur_fqpath.pop() # drop last element (currently selected file, doesn't need re-selection
156 cur_fqpath.reverse()
157 for ce in cur_fqpath:
158 inter_path.append(ce)
159 for i in range(0, len(target_fqpath)-1):
160 te = target_fqpath[i]
161 if te == ce:
162 for te2 in target_fqpath[i+1:]:
163 inter_path.append(te2)
164 # we found our common ancestor
165 return inter_path
166 return None
167
Harald Welteee3501f2021-04-02 13:00:18 +0200168 def get_mf(self) -> Optional['CardMF']:
Harald Welteb2edd142021-01-08 23:29:35 +0100169 """Return the MF (root) of the file system."""
170 if self.parent == None:
171 return None
172 # iterate towards the top. MF has parent == self
173 node = self
Harald Welte1e456572021-04-02 17:16:30 +0200174 while node.parent and node.parent != node:
Harald Welteb2edd142021-01-08 23:29:35 +0100175 node = node.parent
Harald Welte1e456572021-04-02 17:16:30 +0200176 return cast(CardMF, node)
Harald Welteb2edd142021-01-08 23:29:35 +0100177
Harald Weltec91085e2022-02-10 18:05:45 +0100178 def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200179 """Return a dict of {'identifier': self} tuples.
180
181 Args:
182 alias : Add an alias with given name to 'self'
183 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
184 If not specified, all selectables will be returned.
185 Returns:
186 dict containing reference to 'self' for all identifiers.
187 """
Harald Welteb2edd142021-01-08 23:29:35 +0100188 sels = {}
189 if alias:
190 sels.update({alias: self})
Philipp Maier786f7812021-02-25 16:48:10 +0100191 if self.fid and (flags == [] or 'FIDS' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100192 sels.update({self.fid: self})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100193 if self.name and (flags == [] or 'FNAMES' in flags):
Harald Welteb2edd142021-01-08 23:29:35 +0100194 sels.update({self.name: self})
195 return sels
196
Harald Weltec91085e2022-02-10 18:05:45 +0100197 def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
Harald Welteee3501f2021-04-02 13:00:18 +0200198 """Return a dict of {'identifier': File} that is selectable from the current file.
199
200 Args:
201 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
202 If not specified, all selectables will be returned.
203 Returns:
204 dict containing all selectable items. Key is identifier (string), value
205 a reference to a CardFile (or derived class) instance.
206 """
Philipp Maier786f7812021-02-25 16:48:10 +0100207 sels = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100208 # we can always select ourself
Philipp Maier786f7812021-02-25 16:48:10 +0100209 if flags == [] or 'SELF' in flags:
210 sels = self._get_self_selectables('.', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100211 # we can always select our parent
Philipp Maier786f7812021-02-25 16:48:10 +0100212 if flags == [] or 'PARENT' in flags:
Harald Welte1e456572021-04-02 17:16:30 +0200213 if self.parent:
214 sels = self.parent._get_self_selectables('..', flags)
Harald Welteb2edd142021-01-08 23:29:35 +0100215 # if we have a MF, we can always select its applications
Philipp Maier786f7812021-02-25 16:48:10 +0100216 if flags == [] or 'MF' in flags:
217 mf = self.get_mf()
218 if mf:
Harald Weltec91085e2022-02-10 18:05:45 +0100219 sels.update(mf._get_self_selectables(flags=flags))
220 sels.update(mf.get_app_selectables(flags=flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100221 return sels
222
Harald Weltec91085e2022-02-10 18:05:45 +0100223 def get_selectable_names(self, flags=[]) -> List[str]:
Harald Welteee3501f2021-04-02 13:00:18 +0200224 """Return a dict of {'identifier': File} that is selectable from the current file.
225
226 Args:
227 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
228 If not specified, all selectables will be returned.
229 Returns:
Harald Welte1e456572021-04-02 17:16:30 +0200230 list containing all selectable names.
Harald Welteee3501f2021-04-02 13:00:18 +0200231 """
Philipp Maier786f7812021-02-25 16:48:10 +0100232 sels = self.get_selectables(flags)
Harald Welteb3d68c02022-01-21 15:31:29 +0100233 sel_keys = list(sels.keys())
234 sel_keys.sort()
235 return sel_keys
Harald Welteb2edd142021-01-08 23:29:35 +0100236
Harald Weltec91085e2022-02-10 18:05:45 +0100237 def decode_select_response(self, data_hex: str):
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100238 """Decode the response to a SELECT command.
239
240 Args:
Harald Weltec91085e2022-02-10 18:05:45 +0100241 data_hex: Hex string of the select response
242 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100243
Harald Weltec91085e2022-02-10 18:05:45 +0100244 # When the current file does not implement a custom select response decoder,
245 # we just ask the parent file to decode the select response. If this method
246 # is not overloaded by the current file we will again ask the parent file.
247 # This way we recursively travel up the file system tree until we hit a file
248 # that does implement a concrete decoder.
Harald Welte1e456572021-04-02 17:16:30 +0200249 if self.parent:
250 return self.parent.decode_select_response(data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +0100251
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100252 def get_profile(self):
253 """Get the profile associated with this file. If this file does not have any
254 profile assigned, try to find a file above (usually the MF) in the filesystem
255 hirarchy that has a profile assigned
256 """
257
258 # If we have a profile set, return it
259 if self.profile:
260 return self.profile
261
262 # Walk up recursively until we hit a parent that has a profile set
263 if self.parent:
264 return self.parent.get_profile()
265 return None
Harald Welteb2edd142021-01-08 23:29:35 +0100266
Harald Welte9170fbf2022-02-11 21:54:37 +0100267 def should_exist_for_services(self, services: List[int]):
268 """Assuming the provided list of activated services, should this file exist and be activated?."""
269 if self.service is None:
270 return None
271 elif isinstance(self.service, int):
272 # a single service determines the result
273 return self.service in services
274 elif isinstance(self.service, list):
275 # any of the services active -> true
276 for s in self.service:
277 if s in services:
278 return True
279 return False
280 elif isinstance(self.service, tuple):
281 # all of the services active -> true
282 for s in self.service:
283 if not s in services:
284 return False
285 return True
286 else:
287 raise ValueError("self.service must be either int or list or tuple")
288
Harald Weltec91085e2022-02-10 18:05:45 +0100289
Harald Welteb2edd142021-01-08 23:29:35 +0100290class CardDF(CardFile):
291 """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
Philipp Maier63f572d2021-03-09 22:42:47 +0100292
293 @with_default_category('DF/ADF Commands')
294 class ShellCommands(CommandSet):
295 def __init__(self):
296 super().__init__()
297
Harald Welteb2edd142021-01-08 23:29:35 +0100298 def __init__(self, **kwargs):
299 if not isinstance(self, CardADF):
300 if not 'fid' in kwargs:
301 raise TypeError('fid is mandatory for all DF')
302 super().__init__(**kwargs)
303 self.children = dict()
Philipp Maier63f572d2021-03-09 22:42:47 +0100304 self.shell_commands = [self.ShellCommands()]
Harald Welte9170fbf2022-02-11 21:54:37 +0100305 # dict of CardFile affected by service(int), indexed by service
306 self.files_by_service = {}
Harald Welteb2edd142021-01-08 23:29:35 +0100307
308 def __str__(self):
309 return "DF(%s)" % (super().__str__())
310
Harald Welte9170fbf2022-02-11 21:54:37 +0100311 def _add_file_services(self, child):
312 """Add a child (DF/EF) to the files_by_services of the parent."""
313 if not child.service:
314 return
315 if isinstance(child.service, int):
316 self.files_by_service.setdefault(child.service, []).append(child)
317 elif isinstance(child.service, list):
318 for service in child.service:
319 self.files_by_service.setdefault(service, []).append(child)
320 elif isinstance(child.service, tuple):
321 for service in child.service:
322 self.files_by_service.setdefault(service, []).append(child)
323 else:
324 raise ValueError
325
Harald Welted56f45d2022-07-16 11:46:59 +0200326 def _has_service(self):
327 if self.service:
328 return True
329 for c in self.children.values():
330 if isinstance(c, CardDF):
331 if c._has_service():
332 return True
333
Harald Weltec91085e2022-02-10 18:05:45 +0100334 def add_file(self, child: CardFile, ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200335 """Add a child (DF/EF) to this DF.
336 Args:
337 child: The new DF/EF to be added
338 ignore_existing: Ignore, if file with given FID already exists. Old one will be kept.
339 """
Harald Welteb2edd142021-01-08 23:29:35 +0100340 if not isinstance(child, CardFile):
341 raise TypeError("Expected a File instance")
Harald Weltec91085e2022-02-10 18:05:45 +0100342 if not is_hex(child.fid, minlen=4, maxlen=4):
Philipp Maier3aec8712021-03-09 21:49:01 +0100343 raise ValueError("File name %s is not a valid fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100344 if child.name in CardFile.RESERVED_NAMES:
345 raise ValueError("File name %s is a reserved name" % (child.name))
346 if child.fid in CardFile.RESERVED_FIDS:
Philipp Maiere8bc1b42021-03-09 20:33:41 +0100347 raise ValueError("File fid %s is a reserved fid" % (child.fid))
Harald Welteb2edd142021-01-08 23:29:35 +0100348 if child.fid in self.children:
349 if ignore_existing:
350 return
Harald Weltec91085e2022-02-10 18:05:45 +0100351 raise ValueError(
352 "File with given fid %s already exists in %s" % (child.fid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100353 if self.lookup_file_by_sfid(child.sfid):
Harald Weltec91085e2022-02-10 18:05:45 +0100354 raise ValueError(
355 "File with given sfid %s already exists in %s" % (child.sfid, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100356 if self.lookup_file_by_name(child.name):
357 if ignore_existing:
358 return
Harald Weltec91085e2022-02-10 18:05:45 +0100359 raise ValueError(
360 "File with given name %s already exists in %s" % (child.name, self))
Harald Welteb2edd142021-01-08 23:29:35 +0100361 self.children[child.fid] = child
362 child.parent = self
Harald Welte419bb492022-02-12 21:39:35 +0100363 # update the service -> file relationship table
Harald Welte9170fbf2022-02-11 21:54:37 +0100364 self._add_file_services(child)
Harald Welte419bb492022-02-12 21:39:35 +0100365 if isinstance(child, CardDF):
366 for c in child.children.values():
367 self._add_file_services(c)
368 if isinstance(c, CardDF):
Harald Welted56f45d2022-07-16 11:46:59 +0200369 for gc in c.children.values():
370 if isinstance(gc, CardDF):
371 if gc._has_service():
372 raise ValueError('TODO: implement recursive service -> file mapping')
Harald Welteb2edd142021-01-08 23:29:35 +0100373
Harald Weltec91085e2022-02-10 18:05:45 +0100374 def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
Harald Welteee3501f2021-04-02 13:00:18 +0200375 """Add a list of child (DF/EF) to this DF
376
377 Args:
378 children: List of new DF/EFs to be added
379 ignore_existing: Ignore, if file[s] with given FID already exists. Old one[s] will be kept.
380 """
Harald Welteb2edd142021-01-08 23:29:35 +0100381 for child in children:
382 self.add_file(child, ignore_existing)
383
Harald Weltec91085e2022-02-10 18:05:45 +0100384 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200385 """Return a dict of {'identifier': File} that is selectable from the current DF.
386
387 Args:
388 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
389 If not specified, all selectables will be returned.
390 Returns:
391 dict containing all selectable items. Key is identifier (string), value
392 a reference to a CardFile (or derived class) instance.
393 """
Harald Welteb2edd142021-01-08 23:29:35 +0100394 # global selectables + our children
Philipp Maier786f7812021-02-25 16:48:10 +0100395 sels = super().get_selectables(flags)
396 if flags == [] or 'FIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100397 sels.update({x.fid: x for x in self.children.values() if x.fid})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100398 if flags == [] or 'FNAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100399 sels.update({x.name: x for x in self.children.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100400 return sels
401
Harald Weltec91085e2022-02-10 18:05:45 +0100402 def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200403 """Find a file with given name within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100404 if name == None:
405 return None
406 for i in self.children.values():
407 if i.name and i.name == name:
408 return i
409 return None
410
Harald Weltec91085e2022-02-10 18:05:45 +0100411 def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200412 """Find a file with given short file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100413 if sfid == None:
414 return None
415 for i in self.children.values():
Harald Welte1e456572021-04-02 17:16:30 +0200416 if i.sfid == int(str(sfid)):
Harald Welteb2edd142021-01-08 23:29:35 +0100417 return i
418 return None
419
Harald Weltec91085e2022-02-10 18:05:45 +0100420 def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
Harald Welteee3501f2021-04-02 13:00:18 +0200421 """Find a file with given file ID within current DF."""
Harald Welteb2edd142021-01-08 23:29:35 +0100422 if fid in self.children:
423 return self.children[fid]
424 return None
425
426
427class CardMF(CardDF):
428 """MF (Master File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100429
Harald Welteb2edd142021-01-08 23:29:35 +0100430 def __init__(self, **kwargs):
431 # can be overridden; use setdefault
432 kwargs.setdefault('fid', '3f00')
433 kwargs.setdefault('name', 'MF')
434 kwargs.setdefault('desc', 'Master File (directory root)')
435 # cannot be overridden; use assignment
436 kwargs['parent'] = self
437 super().__init__(**kwargs)
438 self.applications = dict()
439
440 def __str__(self):
441 return "MF(%s)" % (self.fid)
442
Harald Weltec91085e2022-02-10 18:05:45 +0100443 def add_application_df(self, app: 'CardADF'):
Harald Welte5ce35242021-04-02 20:27:05 +0200444 """Add an Application to the MF"""
Harald Welteb2edd142021-01-08 23:29:35 +0100445 if not isinstance(app, CardADF):
446 raise TypeError("Expected an ADF instance")
447 if app.aid in self.applications:
448 raise ValueError("AID %s already exists" % (app.aid))
449 self.applications[app.aid] = app
Harald Weltec91085e2022-02-10 18:05:45 +0100450 app.parent = self
Harald Welteb2edd142021-01-08 23:29:35 +0100451
452 def get_app_names(self):
453 """Get list of completions (AID names)"""
Harald Welted53918c2022-02-12 18:20:49 +0100454 return list(self.applications.values())
Harald Welteb2edd142021-01-08 23:29:35 +0100455
Harald Weltec91085e2022-02-10 18:05:45 +0100456 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200457 """Return a dict of {'identifier': File} that is selectable from the current DF.
458
459 Args:
460 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
461 If not specified, all selectables will be returned.
462 Returns:
463 dict containing all selectable items. Key is identifier (string), value
464 a reference to a CardFile (or derived class) instance.
465 """
Philipp Maier786f7812021-02-25 16:48:10 +0100466 sels = super().get_selectables(flags)
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100467 sels.update(self.get_app_selectables(flags))
Harald Welteb2edd142021-01-08 23:29:35 +0100468 return sels
469
Harald Weltec91085e2022-02-10 18:05:45 +0100470 def get_app_selectables(self, flags=[]) -> dict:
Philipp Maier786f7812021-02-25 16:48:10 +0100471 """Get applications by AID + name"""
472 sels = {}
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100473 if flags == [] or 'AIDS' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100474 sels.update({x.aid: x for x in self.applications.values()})
Philipp Maierbd8ed2c2021-03-18 17:09:33 +0100475 if flags == [] or 'ANAMES' in flags:
Harald Weltec91085e2022-02-10 18:05:45 +0100476 sels.update(
477 {x.name: x for x in self.applications.values() if x.name})
Harald Welteb2edd142021-01-08 23:29:35 +0100478 return sels
479
Harald Weltec9752512022-02-11 16:31:15 +0100480 def decode_select_response(self, data_hex: Optional[str]) -> object:
Harald Welteee3501f2021-04-02 13:00:18 +0200481 """Decode the response to a SELECT command.
482
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100483 This is the fall-back method which automatically defers to the standard decoding
484 method defined by the card profile. When no profile is set, then no decoding is
Harald Weltec91085e2022-02-10 18:05:45 +0100485 performed. Specific derived classes (usually ADF) can overload this method to
486 install specific decoding.
Harald Welteee3501f2021-04-02 13:00:18 +0200487 """
Harald Welteb2edd142021-01-08 23:29:35 +0100488
Harald Weltec9752512022-02-11 16:31:15 +0100489 if not data_hex:
490 return data_hex
491
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100492 profile = self.get_profile()
Harald Welteb2edd142021-01-08 23:29:35 +0100493
Philipp Maier5af7bdf2021-11-04 12:48:41 +0100494 if profile:
495 return profile.decode_select_response(data_hex)
496 else:
497 return data_hex
Harald Welteb2edd142021-01-08 23:29:35 +0100498
Harald Weltec91085e2022-02-10 18:05:45 +0100499
Harald Welteb2edd142021-01-08 23:29:35 +0100500class CardADF(CardDF):
501 """ADF (Application Dedicated File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100502
503 def __init__(self, aid: str, **kwargs):
Harald Welteb2edd142021-01-08 23:29:35 +0100504 super().__init__(**kwargs)
Harald Welte5ce35242021-04-02 20:27:05 +0200505 # reference to CardApplication may be set from CardApplication constructor
Harald Weltefe8a7442021-04-10 11:51:54 +0200506 self.application = None # type: Optional[CardApplication]
Harald Welteb2edd142021-01-08 23:29:35 +0100507 self.aid = aid # Application Identifier
Harald Welte1e456572021-04-02 17:16:30 +0200508 mf = self.get_mf()
509 if mf:
Harald Welte5ce35242021-04-02 20:27:05 +0200510 mf.add_application_df(self)
Harald Welteb2edd142021-01-08 23:29:35 +0100511
512 def __str__(self):
Harald Welte4b003652022-07-16 11:55:07 +0200513 return "ADF(%s)" % (self.name if self.name else self.aid)
Harald Welteb2edd142021-01-08 23:29:35 +0100514
Harald Weltec91085e2022-02-10 18:05:45 +0100515 def _path_element(self, prefer_name: bool):
Harald Welteb2edd142021-01-08 23:29:35 +0100516 if self.name and prefer_name:
517 return self.name
518 else:
519 return self.aid
520
521
522class CardEF(CardFile):
523 """EF (Entry File) in the smart card filesystem"""
Harald Weltec91085e2022-02-10 18:05:45 +0100524
Harald Welteb2edd142021-01-08 23:29:35 +0100525 def __init__(self, *, fid, **kwargs):
526 kwargs['fid'] = fid
527 super().__init__(**kwargs)
528
529 def __str__(self):
530 return "EF(%s)" % (super().__str__())
531
Harald Weltec91085e2022-02-10 18:05:45 +0100532 def get_selectables(self, flags=[]) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200533 """Return a dict of {'identifier': File} that is selectable from the current DF.
534
535 Args:
536 flags : Specify which selectables to return 'FIDS' and/or 'NAMES';
537 If not specified, all selectables will be returned.
538 Returns:
539 dict containing all selectable items. Key is identifier (string), value
540 a reference to a CardFile (or derived class) instance.
541 """
Harald Weltec91085e2022-02-10 18:05:45 +0100542 # global selectable names + those of the parent DF
Philipp Maier786f7812021-02-25 16:48:10 +0100543 sels = super().get_selectables(flags)
Harald Welted2c177b2022-07-24 11:35:53 +0200544 if flags == [] or 'FIDS' in flags:
545 sels.update({x.fid: x for x in self.parent.children.values() if x.fid and x != self})
546 if flags == [] or 'FNAMES' in flags:
547 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 +0100548 return sels
549
550
551class TransparentEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200552 """Transparent EF (Entry File) in the smart card filesystem.
553
554 A Transparent EF is a binary file with no formal structure. This is contrary to
555 Record based EFs which have [fixed size] records that can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100556
557 @with_default_category('Transparent EF Commands')
558 class ShellCommands(CommandSet):
Harald Weltec9cdce32021-04-11 10:28:28 +0200559 """Shell commands specific for transparent EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100560
Harald Welteb2edd142021-01-08 23:29:35 +0100561 def __init__(self):
562 super().__init__()
563
Harald Welteaefd0642022-02-25 15:26:37 +0100564 dec_hex_parser = argparse.ArgumentParser()
565 dec_hex_parser.add_argument('--oneline', action='store_true',
566 help='No JSON pretty-printing, dump as a single line')
567 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
568
569 @cmd2.with_argparser(dec_hex_parser)
570 def do_decode_hex(self, opts):
571 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200572 data = self._cmd.lchan.selected_file.decode_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100573 self._cmd.poutput_json(data, opts.oneline)
574
Harald Welteb2edd142021-01-08 23:29:35 +0100575 read_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100576 read_bin_parser.add_argument(
577 '--offset', type=int, default=0, help='Byte offset for start of read')
578 read_bin_parser.add_argument(
579 '--length', type=int, help='Number of bytes to read')
580
Harald Welteb2edd142021-01-08 23:29:35 +0100581 @cmd2.with_argparser(read_bin_parser)
582 def do_read_binary(self, opts):
583 """Read binary data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200584 (data, sw) = self._cmd.lchan.read_binary(opts.length, opts.offset)
Harald Welteb2edd142021-01-08 23:29:35 +0100585 self._cmd.poutput(data)
586
Harald Weltebcad86c2021-04-06 20:08:39 +0200587 read_bin_dec_parser = argparse.ArgumentParser()
588 read_bin_dec_parser.add_argument('--oneline', action='store_true',
589 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100590
Harald Weltebcad86c2021-04-06 20:08:39 +0200591 @cmd2.with_argparser(read_bin_dec_parser)
Harald Welteb2edd142021-01-08 23:29:35 +0100592 def do_read_binary_decoded(self, opts):
593 """Read + decode data from a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200594 (data, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte1748b932021-04-06 21:12:25 +0200595 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100596
597 upd_bin_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100598 upd_bin_parser.add_argument(
599 '--offset', type=int, default=0, help='Byte offset for start of read')
600 upd_bin_parser.add_argument(
601 'data', help='Data bytes (hex format) to write')
602
Harald Welteb2edd142021-01-08 23:29:35 +0100603 @cmd2.with_argparser(upd_bin_parser)
604 def do_update_binary(self, opts):
605 """Update (Write) data of a transparent EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200606 (data, sw) = self._cmd.lchan.update_binary(opts.data, opts.offset)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100607 if data:
608 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100609
610 upd_bin_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100611 upd_bin_dec_parser.add_argument(
612 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200613 upd_bin_dec_parser.add_argument('--json-path', type=str,
614 help='JSON path to modify specific element of file only')
Harald Weltec91085e2022-02-10 18:05:45 +0100615
Harald Welteb2edd142021-01-08 23:29:35 +0100616 @cmd2.with_argparser(upd_bin_dec_parser)
617 def do_update_binary_decoded(self, opts):
618 """Encode + Update (Write) data of a transparent EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200619 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200620 (data_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Weltec91085e2022-02-10 18:05:45 +0100621 js_path_modify(data_json, opts.json_path,
622 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200623 else:
624 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200625 (data, sw) = self._cmd.lchan.update_binary_dec(data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100626 if data:
Harald Welte1748b932021-04-06 21:12:25 +0200627 self._cmd.poutput_json(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100628
Harald Welte4145d3c2021-04-08 20:34:13 +0200629 def do_edit_binary_decoded(self, opts):
630 """Edit the JSON representation of the EF contents in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200631 (orig_json, sw) = self._cmd.lchan.read_binary_dec()
Harald Welte4145d3c2021-04-08 20:34:13 +0200632 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
633 filename = '%s/file' % dirname
634 # write existing data as JSON to file
635 with open(filename, 'w') as text_file:
636 json.dump(orig_json, text_file, indent=4)
637 # run a text editor
638 self._cmd._run_editor(filename)
639 with open(filename, 'r') as text_file:
640 edited_json = json.load(text_file)
641 if edited_json == orig_json:
642 self._cmd.poutput("Data not modified, skipping write")
643 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200644 (data, sw) = self._cmd.lchan.update_binary_dec(edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200645 if data:
646 self._cmd.poutput_json(data)
647
Harald Weltec91085e2022-02-10 18:05:45 +0100648 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte13edf302022-07-21 15:19:23 +0200649 size: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200650 """
651 Args:
652 fid : File Identifier (4 hex digits)
653 sfid : Short File Identifier (2 hex digits, optional)
654 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200655 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200656 parent : Parent CardFile object within filesystem hierarchy
657 size : tuple of (minimum_size, recommended_size)
658 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100659 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200660 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200661 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100662 self.size = size
663 self.shell_commands = [self.ShellCommands()]
664
Harald Weltec91085e2022-02-10 18:05:45 +0100665 def decode_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200666 """Decode raw (binary) data into abstract representation.
667
668 A derived class would typically provide a _decode_bin() or _decode_hex() method
669 for implementing this specifically for the given file. This function checks which
670 of the method exists, add calls them (with conversion, as needed).
671
672 Args:
673 raw_bin_data : binary encoded data
674 Returns:
675 abstract_data; dict representing the decoded data
676 """
Harald Welteb2edd142021-01-08 23:29:35 +0100677 method = getattr(self, '_decode_bin', None)
678 if callable(method):
679 return method(raw_bin_data)
680 method = getattr(self, '_decode_hex', None)
681 if callable(method):
682 return method(b2h(raw_bin_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200683 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200684 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200685 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100686 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100687 t.from_tlv(raw_bin_data)
688 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100689 return {'raw': raw_bin_data.hex()}
690
Harald Weltec91085e2022-02-10 18:05:45 +0100691 def decode_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200692 """Decode raw (hex string) data into abstract representation.
693
694 A derived class would typically provide a _decode_bin() or _decode_hex() method
695 for implementing this specifically for the given file. This function checks which
696 of the method exists, add calls them (with conversion, as needed).
697
698 Args:
699 raw_hex_data : hex-encoded data
700 Returns:
701 abstract_data; dict representing the decoded data
702 """
Harald Welteb2edd142021-01-08 23:29:35 +0100703 method = getattr(self, '_decode_hex', None)
704 if callable(method):
705 return method(raw_hex_data)
706 raw_bin_data = h2b(raw_hex_data)
707 method = getattr(self, '_decode_bin', None)
708 if callable(method):
709 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200710 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200711 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200712 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100713 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100714 t.from_tlv(raw_bin_data)
715 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100716 return {'raw': raw_bin_data.hex()}
717
Harald Weltec91085e2022-02-10 18:05:45 +0100718 def encode_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +0200719 """Encode abstract representation into raw (binary) data.
720
721 A derived class would typically provide an _encode_bin() or _encode_hex() method
722 for implementing this specifically for the given file. This function checks which
723 of the method exists, add calls them (with conversion, as needed).
724
725 Args:
726 abstract_data : dict representing the decoded data
727 Returns:
728 binary encoded data
729 """
Harald Welteb2edd142021-01-08 23:29:35 +0100730 method = getattr(self, '_encode_bin', None)
731 if callable(method):
732 return method(abstract_data)
733 method = getattr(self, '_encode_hex', None)
734 if callable(method):
735 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +0200736 if self._construct:
737 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +0200738 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100739 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100740 t.from_dict(abstract_data)
741 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +0100742 raise NotImplementedError(
743 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100744
Harald Weltec91085e2022-02-10 18:05:45 +0100745 def encode_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200746 """Encode abstract representation into raw (hex string) data.
747
748 A derived class would typically provide an _encode_bin() or _encode_hex() method
749 for implementing this specifically for the given file. This function checks which
750 of the method exists, add calls them (with conversion, as needed).
751
752 Args:
753 abstract_data : dict representing the decoded data
754 Returns:
755 hex string encoded data
756 """
Harald Welteb2edd142021-01-08 23:29:35 +0100757 method = getattr(self, '_encode_hex', None)
758 if callable(method):
759 return method(abstract_data)
760 method = getattr(self, '_encode_bin', None)
761 if callable(method):
762 raw_bin_data = method(abstract_data)
763 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200764 if self._construct:
765 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +0200766 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100767 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100768 t.from_dict(abstract_data)
769 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +0100770 raise NotImplementedError(
771 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +0100772
773
774class LinFixedEF(CardEF):
Harald Welteee3501f2021-04-02 13:00:18 +0200775 """Linear Fixed EF (Entry File) in the smart card filesystem.
776
777 Linear Fixed EFs are record oriented files. They consist of a number of fixed-size
778 records. The records can be individually read/updated."""
Harald Welteb2edd142021-01-08 23:29:35 +0100779
780 @with_default_category('Linear Fixed EF Commands')
781 class ShellCommands(CommandSet):
Harald Welteee3501f2021-04-02 13:00:18 +0200782 """Shell commands specific for Linear Fixed EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +0100783
Harald Welte9170fbf2022-02-11 21:54:37 +0100784 def __init__(self, **kwargs):
785 super().__init__(**kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100786
Harald Welteaefd0642022-02-25 15:26:37 +0100787 dec_hex_parser = argparse.ArgumentParser()
788 dec_hex_parser.add_argument('--oneline', action='store_true',
789 help='No JSON pretty-printing, dump as a single line')
790 dec_hex_parser.add_argument('HEXSTR', help='Hex-string of encoded data to decode')
791
792 @cmd2.with_argparser(dec_hex_parser)
793 def do_decode_hex(self, opts):
794 """Decode command-line provided hex-string as if it was read from the file."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200795 data = self._cmd.lchan.selected_file.decode_record_hex(opts.HEXSTR)
Harald Welteaefd0642022-02-25 15:26:37 +0100796 self._cmd.poutput_json(data, opts.oneline)
797
Harald Welteb2edd142021-01-08 23:29:35 +0100798 read_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100799 read_rec_parser.add_argument(
800 'record_nr', type=int, help='Number of record to be read')
801 read_rec_parser.add_argument(
802 '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
803
Harald Welteb2edd142021-01-08 23:29:35 +0100804 @cmd2.with_argparser(read_rec_parser)
805 def do_read_record(self, opts):
Philipp Maier41555732021-02-25 16:52:08 +0100806 """Read one or multiple records from a record-oriented EF"""
807 for r in range(opts.count):
808 recnr = opts.record_nr + r
Harald Weltea6c0f882022-07-17 14:23:17 +0200809 (data, sw) = self._cmd.lchan.read_record(recnr)
Philipp Maier41555732021-02-25 16:52:08 +0100810 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100811 recstr = str(data)
Philipp Maier41555732021-02-25 16:52:08 +0100812 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100813 recstr = "(empty)"
Philipp Maier41555732021-02-25 16:52:08 +0100814 self._cmd.poutput("%03d %s" % (recnr, recstr))
Harald Welteb2edd142021-01-08 23:29:35 +0100815
816 read_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100817 read_rec_dec_parser.add_argument(
818 'record_nr', type=int, help='Number of record to be read')
Harald Weltebcad86c2021-04-06 20:08:39 +0200819 read_rec_dec_parser.add_argument('--oneline', action='store_true',
820 help='No JSON pretty-printing, dump as a single line')
Harald Weltec91085e2022-02-10 18:05:45 +0100821
Harald Welteb2edd142021-01-08 23:29:35 +0100822 @cmd2.with_argparser(read_rec_dec_parser)
823 def do_read_record_decoded(self, opts):
824 """Read + decode a record from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200825 (data, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Welte1748b932021-04-06 21:12:25 +0200826 self._cmd.poutput_json(data, opts.oneline)
Harald Welteb2edd142021-01-08 23:29:35 +0100827
Harald Welte850b72a2021-04-07 09:33:03 +0200828 read_recs_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100829
Harald Welte850b72a2021-04-07 09:33:03 +0200830 @cmd2.with_argparser(read_recs_parser)
831 def do_read_records(self, opts):
832 """Read all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200833 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200834 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200835 (data, sw) = self._cmd.lchan.read_record(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200836 if (len(data) > 0):
Harald Weltec91085e2022-02-10 18:05:45 +0100837 recstr = str(data)
Harald Welte850b72a2021-04-07 09:33:03 +0200838 else:
Harald Weltec91085e2022-02-10 18:05:45 +0100839 recstr = "(empty)"
Harald Welte850b72a2021-04-07 09:33:03 +0200840 self._cmd.poutput("%03d %s" % (recnr, recstr))
841
842 read_recs_dec_parser = argparse.ArgumentParser()
843 read_recs_dec_parser.add_argument('--oneline', action='store_true',
Harald Weltec91085e2022-02-10 18:05:45 +0100844 help='No JSON pretty-printing, dump as a single line')
845
Harald Welte850b72a2021-04-07 09:33:03 +0200846 @cmd2.with_argparser(read_recs_dec_parser)
847 def do_read_records_decoded(self, opts):
848 """Read + decode all records from a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200849 num_of_rec = self._cmd.lchan.selected_file_num_of_rec()
Harald Welte850b72a2021-04-07 09:33:03 +0200850 # collect all results in list so they are rendered as JSON list when printing
851 data_list = []
852 for recnr in range(1, 1 + num_of_rec):
Harald Weltea6c0f882022-07-17 14:23:17 +0200853 (data, sw) = self._cmd.lchan.read_record_dec(recnr)
Harald Welte850b72a2021-04-07 09:33:03 +0200854 data_list.append(data)
855 self._cmd.poutput_json(data_list, opts.oneline)
856
Harald Welteb2edd142021-01-08 23:29:35 +0100857 upd_rec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100858 upd_rec_parser.add_argument(
859 'record_nr', type=int, help='Number of record to be read')
860 upd_rec_parser.add_argument(
861 'data', help='Data bytes (hex format) to write')
862
Harald Welteb2edd142021-01-08 23:29:35 +0100863 @cmd2.with_argparser(upd_rec_parser)
864 def do_update_record(self, opts):
865 """Update (write) data to a record-oriented EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +0200866 (data, sw) = self._cmd.lchan.update_record(opts.record_nr, opts.data)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100867 if data:
868 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100869
870 upd_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100871 upd_rec_dec_parser.add_argument(
872 'record_nr', type=int, help='Number of record to be read')
873 upd_rec_dec_parser.add_argument(
874 'data', help='Abstract data (JSON format) to write')
Harald Welte0d4e98a2021-04-07 00:14:40 +0200875 upd_rec_dec_parser.add_argument('--json-path', type=str,
876 help='JSON path to modify specific element of record only')
Harald Weltec91085e2022-02-10 18:05:45 +0100877
Harald Welteb2edd142021-01-08 23:29:35 +0100878 @cmd2.with_argparser(upd_rec_dec_parser)
879 def do_update_record_decoded(self, opts):
880 """Encode + Update (write) data to a record-oriented EF"""
Harald Welte0d4e98a2021-04-07 00:14:40 +0200881 if opts.json_path:
Harald Weltea6c0f882022-07-17 14:23:17 +0200882 (data_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Harald Weltec91085e2022-02-10 18:05:45 +0100883 js_path_modify(data_json, opts.json_path,
884 json.loads(opts.data))
Harald Welte0d4e98a2021-04-07 00:14:40 +0200885 else:
886 data_json = json.loads(opts.data)
Harald Weltea6c0f882022-07-17 14:23:17 +0200887 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100888 opts.record_nr, data_json)
Philipp Maiere6bc4f92021-03-11 17:13:46 +0100889 if data:
890 self._cmd.poutput(data)
Harald Welteb2edd142021-01-08 23:29:35 +0100891
Harald Welte4145d3c2021-04-08 20:34:13 +0200892 edit_rec_dec_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +0100893 edit_rec_dec_parser.add_argument(
894 'record_nr', type=int, help='Number of record to be edited')
895
Harald Welte4145d3c2021-04-08 20:34:13 +0200896 @cmd2.with_argparser(edit_rec_dec_parser)
897 def do_edit_record_decoded(self, opts):
898 """Edit the JSON representation of one record in an editor."""
Harald Weltea6c0f882022-07-17 14:23:17 +0200899 (orig_json, sw) = self._cmd.lchan.read_record_dec(opts.record_nr)
Vadim Yanitskiy895fa6f2021-05-02 02:36:44 +0200900 with tempfile.TemporaryDirectory(prefix='pysim_') as dirname:
Harald Welte4145d3c2021-04-08 20:34:13 +0200901 filename = '%s/file' % dirname
902 # write existing data as JSON to file
903 with open(filename, 'w') as text_file:
904 json.dump(orig_json, text_file, indent=4)
905 # run a text editor
906 self._cmd._run_editor(filename)
907 with open(filename, 'r') as text_file:
908 edited_json = json.load(text_file)
909 if edited_json == orig_json:
910 self._cmd.poutput("Data not modified, skipping write")
911 else:
Harald Weltea6c0f882022-07-17 14:23:17 +0200912 (data, sw) = self._cmd.lchan.update_record_dec(
Harald Weltec91085e2022-02-10 18:05:45 +0100913 opts.record_nr, edited_json)
Harald Welte4145d3c2021-04-08 20:34:13 +0200914 if data:
915 self._cmd.poutput_json(data)
Harald Welte4145d3c2021-04-08 20:34:13 +0200916
Harald Weltec91085e2022-02-10 18:05:45 +0100917 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
Harald Welte99e4cc02022-07-21 15:25:47 +0200918 parent: Optional[CardDF] = None, rec_len: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +0200919 """
920 Args:
921 fid : File Identifier (4 hex digits)
922 sfid : Short File Identifier (2 hex digits, optional)
923 name : Brief name of the file, lik EF_ICCID
Harald Weltec9cdce32021-04-11 10:28:28 +0200924 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +0200925 parent : Parent CardFile object within filesystem hierarchy
Harald Welte99e4cc02022-07-21 15:25:47 +0200926 rec_len : Tuple of (minimum_length, recommended_length)
Harald Welteee3501f2021-04-02 13:00:18 +0200927 """
Harald Welte9170fbf2022-02-11 21:54:37 +0100928 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +0100929 self.rec_len = rec_len
930 self.shell_commands = [self.ShellCommands()]
Harald Welte2db5cfb2021-04-10 19:05:37 +0200931 self._construct = None
Harald Weltefb506212021-05-29 21:28:24 +0200932 self._tlv = None
Harald Welteb2edd142021-01-08 23:29:35 +0100933
Harald Weltec91085e2022-02-10 18:05:45 +0100934 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200935 """Decode raw (hex string) data into abstract representation.
936
937 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
938 method for implementing this specifically for the given file. This function checks which
939 of the method exists, add calls them (with conversion, as needed).
940
941 Args:
942 raw_hex_data : hex-encoded data
943 Returns:
944 abstract_data; dict representing the decoded data
945 """
Harald Welteb2edd142021-01-08 23:29:35 +0100946 method = getattr(self, '_decode_record_hex', None)
947 if callable(method):
948 return method(raw_hex_data)
949 raw_bin_data = h2b(raw_hex_data)
950 method = getattr(self, '_decode_record_bin', None)
951 if callable(method):
952 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200953 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200954 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200955 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100956 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100957 t.from_tlv(raw_bin_data)
958 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100959 return {'raw': raw_bin_data.hex()}
960
Harald Weltec91085e2022-02-10 18:05:45 +0100961 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +0200962 """Decode raw (binary) data into abstract representation.
963
964 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
965 method for implementing this specifically for the given file. This function checks which
966 of the method exists, add calls them (with conversion, as needed).
967
968 Args:
969 raw_bin_data : binary encoded data
970 Returns:
971 abstract_data; dict representing the decoded data
972 """
Harald Welteb2edd142021-01-08 23:29:35 +0100973 method = getattr(self, '_decode_record_bin', None)
974 if callable(method):
975 return method(raw_bin_data)
976 raw_hex_data = b2h(raw_bin_data)
977 method = getattr(self, '_decode_record_hex', None)
978 if callable(method):
979 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +0200980 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +0200981 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +0200982 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +0100983 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +0100984 t.from_tlv(raw_bin_data)
985 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +0100986 return {'raw': raw_hex_data}
987
Harald Weltec91085e2022-02-10 18:05:45 +0100988 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +0200989 """Encode abstract representation into raw (hex string) data.
990
991 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
992 method for implementing this specifically for the given file. This function checks which
993 of the method exists, add calls them (with conversion, as needed).
994
995 Args:
996 abstract_data : dict representing the decoded data
997 Returns:
998 hex string encoded data
999 """
Harald Welteb2edd142021-01-08 23:29:35 +01001000 method = getattr(self, '_encode_record_hex', None)
1001 if callable(method):
1002 return method(abstract_data)
1003 method = getattr(self, '_encode_record_bin', None)
1004 if callable(method):
1005 raw_bin_data = method(abstract_data)
Harald Welte1e456572021-04-02 17:16:30 +02001006 return b2h(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001007 if self._construct:
1008 return b2h(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001009 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001010 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001011 t.from_dict(abstract_data)
1012 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001013 raise NotImplementedError(
1014 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001015
Harald Weltec91085e2022-02-10 18:05:45 +01001016 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001017 """Encode abstract representation into raw (binary) data.
1018
1019 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1020 method for implementing this specifically for the given file. This function checks which
1021 of the method exists, add calls them (with conversion, as needed).
1022
1023 Args:
1024 abstract_data : dict representing the decoded data
1025 Returns:
1026 binary encoded data
1027 """
Harald Welteb2edd142021-01-08 23:29:35 +01001028 method = getattr(self, '_encode_record_bin', None)
1029 if callable(method):
1030 return method(abstract_data)
1031 method = getattr(self, '_encode_record_hex', None)
1032 if callable(method):
Harald Welteee3501f2021-04-02 13:00:18 +02001033 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001034 if self._construct:
1035 return self._construct.build(abstract_data)
Harald Weltefb506212021-05-29 21:28:24 +02001036 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001037 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001038 t.from_dict(abstract_data)
1039 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001040 raise NotImplementedError(
1041 "%s encoder not yet implemented. Patches welcome." % self)
1042
Harald Welteb2edd142021-01-08 23:29:35 +01001043
1044class CyclicEF(LinFixedEF):
1045 """Cyclic EF (Entry File) in the smart card filesystem"""
1046 # we don't really have any special support for those; just recycling LinFixedEF here
Harald Weltec91085e2022-02-10 18:05:45 +01001047
1048 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte99e4cc02022-07-21 15:25:47 +02001049 rec_len: Size = (1, None), **kwargs):
Harald Welte9170fbf2022-02-11 21:54:37 +01001050 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +01001051
Harald Welteb2edd142021-01-08 23:29:35 +01001052
1053class TransRecEF(TransparentEF):
1054 """Transparent EF (Entry File) containing fixed-size records.
Harald Welteee3501f2021-04-02 13:00:18 +02001055
Harald Welteb2edd142021-01-08 23:29:35 +01001056 These are the real odd-balls and mostly look like mistakes in the specification:
1057 Specified as 'transparent' EF, but actually containing several fixed-length records
1058 inside.
1059 We add a special class for those, so the user only has to provide encoder/decoder functions
1060 for a record, while this class takes care of split / merge of records.
1061 """
Harald Weltec91085e2022-02-10 18:05:45 +01001062
1063 def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
Harald Welte13edf302022-07-21 15:19:23 +02001064 parent: Optional[CardDF] = None, size: Size = (1, None), **kwargs):
Harald Welteee3501f2021-04-02 13:00:18 +02001065 """
1066 Args:
1067 fid : File Identifier (4 hex digits)
1068 sfid : Short File Identifier (2 hex digits, optional)
Harald Weltec9cdce32021-04-11 10:28:28 +02001069 name : Brief name of the file, like EF_ICCID
1070 desc : Description of the file
Harald Welteee3501f2021-04-02 13:00:18 +02001071 parent : Parent CardFile object within filesystem hierarchy
1072 rec_len : Length of the fixed-length records within transparent EF
1073 size : tuple of (minimum_size, recommended_size)
1074 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001075 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size, **kwargs)
Harald Welteb2edd142021-01-08 23:29:35 +01001076 self.rec_len = rec_len
1077
Harald Weltec91085e2022-02-10 18:05:45 +01001078 def decode_record_hex(self, raw_hex_data: str) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001079 """Decode raw (hex string) data into abstract representation.
1080
1081 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1082 method for implementing this specifically for the given file. This function checks which
1083 of the method exists, add calls them (with conversion, as needed).
1084
1085 Args:
1086 raw_hex_data : hex-encoded data
1087 Returns:
1088 abstract_data; dict representing the decoded data
1089 """
Harald Welteb2edd142021-01-08 23:29:35 +01001090 method = getattr(self, '_decode_record_hex', None)
1091 if callable(method):
1092 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001093 raw_bin_data = h2b(raw_hex_data)
Harald Welteb2edd142021-01-08 23:29:35 +01001094 method = getattr(self, '_decode_record_bin', None)
1095 if callable(method):
Harald Welteb2edd142021-01-08 23:29:35 +01001096 return method(raw_bin_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001097 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001098 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001099 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001100 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001101 t.from_tlv(raw_bin_data)
1102 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001103 return {'raw': raw_hex_data}
1104
Harald Weltec91085e2022-02-10 18:05:45 +01001105 def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
Harald Welteee3501f2021-04-02 13:00:18 +02001106 """Decode raw (binary) data into abstract representation.
1107
1108 A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
1109 method for implementing this specifically for the given file. This function checks which
1110 of the method exists, add calls them (with conversion, as needed).
1111
1112 Args:
1113 raw_bin_data : binary encoded data
1114 Returns:
1115 abstract_data; dict representing the decoded data
1116 """
Harald Welteb2edd142021-01-08 23:29:35 +01001117 method = getattr(self, '_decode_record_bin', None)
1118 if callable(method):
1119 return method(raw_bin_data)
1120 raw_hex_data = b2h(raw_bin_data)
1121 method = getattr(self, '_decode_record_hex', None)
1122 if callable(method):
1123 return method(raw_hex_data)
Harald Welte2db5cfb2021-04-10 19:05:37 +02001124 if self._construct:
Harald Welte07c7b1f2021-05-28 22:01:29 +02001125 return parse_construct(self._construct, raw_bin_data)
Harald Weltefb506212021-05-29 21:28:24 +02001126 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001127 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001128 t.from_tlv(raw_bin_data)
1129 return t.to_dict()
Harald Welteb2edd142021-01-08 23:29:35 +01001130 return {'raw': raw_hex_data}
1131
Harald Weltec91085e2022-02-10 18:05:45 +01001132 def encode_record_hex(self, abstract_data: dict) -> str:
Harald Welteee3501f2021-04-02 13:00:18 +02001133 """Encode abstract representation into raw (hex string) data.
1134
1135 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1136 method for implementing this specifically for the given file. This function checks which
1137 of the method exists, add calls them (with conversion, as needed).
1138
1139 Args:
1140 abstract_data : dict representing the decoded data
1141 Returns:
1142 hex string encoded data
1143 """
Harald Welteb2edd142021-01-08 23:29:35 +01001144 method = getattr(self, '_encode_record_hex', None)
1145 if callable(method):
1146 return method(abstract_data)
1147 method = getattr(self, '_encode_record_bin', None)
1148 if callable(method):
Harald Welte1e456572021-04-02 17:16:30 +02001149 return b2h(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001150 if self._construct:
1151 return b2h(filter_dict(self._construct.build(abstract_data)))
Harald Weltefb506212021-05-29 21:28:24 +02001152 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001153 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001154 t.from_dict(abstract_data)
1155 return b2h(t.to_tlv())
Harald Weltec91085e2022-02-10 18:05:45 +01001156 raise NotImplementedError(
1157 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001158
Harald Weltec91085e2022-02-10 18:05:45 +01001159 def encode_record_bin(self, abstract_data: dict) -> bytearray:
Harald Welteee3501f2021-04-02 13:00:18 +02001160 """Encode abstract representation into raw (binary) data.
1161
1162 A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
1163 method for implementing this specifically for the given file. This function checks which
1164 of the method exists, add calls them (with conversion, as needed).
1165
1166 Args:
1167 abstract_data : dict representing the decoded data
1168 Returns:
1169 binary encoded data
1170 """
Harald Welteb2edd142021-01-08 23:29:35 +01001171 method = getattr(self, '_encode_record_bin', None)
1172 if callable(method):
1173 return method(abstract_data)
1174 method = getattr(self, '_encode_record_hex', None)
1175 if callable(method):
1176 return h2b(method(abstract_data))
Harald Welte2db5cfb2021-04-10 19:05:37 +02001177 if self._construct:
1178 return filter_dict(self._construct.build(abstract_data))
Harald Weltefb506212021-05-29 21:28:24 +02001179 elif self._tlv:
Harald Welteca60ac22022-02-10 18:01:02 +01001180 t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
Harald Welte944cd2f2022-01-21 16:01:29 +01001181 t.from_dict(abstract_data)
1182 return t.to_tlv()
Harald Weltec91085e2022-02-10 18:05:45 +01001183 raise NotImplementedError(
1184 "%s encoder not yet implemented. Patches welcome." % self)
Harald Welteb2edd142021-01-08 23:29:35 +01001185
Harald Weltec91085e2022-02-10 18:05:45 +01001186 def _decode_bin(self, raw_bin_data: bytearray):
1187 chunks = [raw_bin_data[i:i+self.rec_len]
1188 for i in range(0, len(raw_bin_data), self.rec_len)]
Harald Welteb2edd142021-01-08 23:29:35 +01001189 return [self.decode_record_bin(x) for x in chunks]
1190
Harald Welteee3501f2021-04-02 13:00:18 +02001191 def _encode_bin(self, abstract_data) -> bytes:
Harald Welteb2edd142021-01-08 23:29:35 +01001192 chunks = [self.encode_record_bin(x) for x in abstract_data]
1193 # FIXME: pad to file size
1194 return b''.join(chunks)
1195
1196
Harald Welte917d98c2021-04-21 11:51:25 +02001197class BerTlvEF(CardEF):
Harald Welte27881622021-04-21 11:16:31 +02001198 """BER-TLV EF (Entry File) in the smart card filesystem.
1199 A BER-TLV EF is a binary file with a BER (Basic Encoding Rules) TLV structure
Harald Welteb2edd142021-01-08 23:29:35 +01001200
Harald Welte27881622021-04-21 11:16:31 +02001201 NOTE: We currently don't really support those, this class is simply a wrapper
1202 around TransparentEF as a place-holder, so we can already define EFs of BER-TLV
1203 type without fully supporting them."""
Harald Welteb2edd142021-01-08 23:29:35 +01001204
Harald Welte917d98c2021-04-21 11:51:25 +02001205 @with_default_category('BER-TLV EF Commands')
1206 class ShellCommands(CommandSet):
1207 """Shell commands specific for BER-TLV EFs."""
Harald Weltec91085e2022-02-10 18:05:45 +01001208
Harald Welte917d98c2021-04-21 11:51:25 +02001209 def __init__(self):
1210 super().__init__()
1211
1212 retrieve_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001213 retrieve_data_parser.add_argument(
1214 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
1215
Harald Welte917d98c2021-04-21 11:51:25 +02001216 @cmd2.with_argparser(retrieve_data_parser)
1217 def do_retrieve_data(self, opts):
1218 """Retrieve (Read) data from a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001219 (data, sw) = self._cmd.lchan.retrieve_data(opts.tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001220 self._cmd.poutput(data)
1221
1222 def do_retrieve_tags(self, opts):
1223 """List tags available in a given BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001224 tags = self._cmd.lchan.retrieve_tags()
Harald Welte917d98c2021-04-21 11:51:25 +02001225 self._cmd.poutput(tags)
1226
1227 set_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001228 set_data_parser.add_argument(
1229 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1230 set_data_parser.add_argument(
1231 'data', help='Data bytes (hex format) to write')
1232
Harald Welte917d98c2021-04-21 11:51:25 +02001233 @cmd2.with_argparser(set_data_parser)
1234 def do_set_data(self, opts):
1235 """Set (Write) data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001236 (data, sw) = self._cmd.lchan.set_data(opts.tag, opts.data)
Harald Welte917d98c2021-04-21 11:51:25 +02001237 if data:
1238 self._cmd.poutput(data)
1239
1240 del_data_parser = argparse.ArgumentParser()
Harald Weltec91085e2022-02-10 18:05:45 +01001241 del_data_parser.add_argument(
1242 'tag', type=auto_int, help='BER-TLV Tag of value to set')
1243
Harald Welte917d98c2021-04-21 11:51:25 +02001244 @cmd2.with_argparser(del_data_parser)
1245 def do_delete_data(self, opts):
1246 """Delete data for a given tag in a BER-TLV EF"""
Harald Weltea6c0f882022-07-17 14:23:17 +02001247 (data, sw) = self._cmd.lchan.set_data(opts.tag, None)
Harald Welte917d98c2021-04-21 11:51:25 +02001248 if data:
1249 self._cmd.poutput(data)
1250
Harald Weltec91085e2022-02-10 18:05:45 +01001251 def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
Harald Welte13edf302022-07-21 15:19:23 +02001252 size: Size = (1, None), **kwargs):
Harald Welte917d98c2021-04-21 11:51:25 +02001253 """
1254 Args:
1255 fid : File Identifier (4 hex digits)
1256 sfid : Short File Identifier (2 hex digits, optional)
1257 name : Brief name of the file, lik EF_ICCID
1258 desc : Description of the file
1259 parent : Parent CardFile object within filesystem hierarchy
1260 size : tuple of (minimum_size, recommended_size)
1261 """
Harald Welte9170fbf2022-02-11 21:54:37 +01001262 super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, **kwargs)
Harald Welte917d98c2021-04-21 11:51:25 +02001263 self._construct = None
1264 self.size = size
1265 self.shell_commands = [self.ShellCommands()]
1266
Harald Welteb2edd142021-01-08 23:29:35 +01001267
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001268class RuntimeState:
Harald Welteb2edd142021-01-08 23:29:35 +01001269 """Represent the runtime state of a session with a card."""
Harald Weltec91085e2022-02-10 18:05:45 +01001270
1271 def __init__(self, card, profile: 'CardProfile'):
Harald Welteee3501f2021-04-02 13:00:18 +02001272 """
1273 Args:
1274 card : pysim.cards.Card instance
1275 profile : CardProfile instance
1276 """
Philipp Maier5af7bdf2021-11-04 12:48:41 +01001277 self.mf = CardMF(profile=profile)
Harald Welteb2edd142021-01-08 23:29:35 +01001278 self.card = card
Harald Welteb2edd142021-01-08 23:29:35 +01001279 self.profile = profile
Harald Weltea6c0f882022-07-17 14:23:17 +02001280 self.lchan = {}
1281 # the basic logical channel always exists
1282 self.lchan[0] = RuntimeLchan(0, self)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001283
1284 # make sure the class and selection control bytes, which are specified
1285 # by the card profile are used
Harald Weltec91085e2022-02-10 18:05:45 +01001286 self.card.set_apdu_parameter(
1287 cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
Philipp Maier51cad0d2021-11-08 15:45:10 +01001288
Harald Welte5ce35242021-04-02 20:27:05 +02001289 # add application ADFs + MF-files from profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001290 apps = self._match_applications()
1291 for a in apps:
Harald Welte5ce35242021-04-02 20:27:05 +02001292 if a.adf:
1293 self.mf.add_application_df(a.adf)
Harald Welteb2edd142021-01-08 23:29:35 +01001294 for f in self.profile.files_in_mf:
1295 self.mf.add_file(f)
Philipp Maier38c74f62021-03-17 17:19:52 +01001296 self.conserve_write = True
Harald Welteb2edd142021-01-08 23:29:35 +01001297
Philipp Maier4e2e1d92021-11-08 15:36:01 +01001298 # make sure that when the runtime state is created, the card is also
1299 # in a defined state.
1300 self.reset()
1301
Philipp Maier1e896f32021-03-10 17:02:53 +01001302 def _match_applications(self):
1303 """match the applications from the profile with applications on the card"""
1304 apps_profile = self.profile.applications
Philipp Maierd454fe72021-11-08 15:32:23 +01001305
1306 # When the profile does not feature any applications, then we are done already
1307 if not apps_profile:
1308 return []
1309
1310 # Read AIDs from card and match them against the applications defined by the
1311 # card profile
Philipp Maier1e896f32021-03-10 17:02:53 +01001312 aids_card = self.card.read_aids()
1313 apps_taken = []
1314 if aids_card:
1315 aids_taken = []
1316 print("AIDs on card:")
1317 for a in aids_card:
1318 for f in apps_profile:
1319 if f.aid in a:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001320 print(" %s: %s (EF.DIR)" % (f.name, a))
Philipp Maier1e896f32021-03-10 17:02:53 +01001321 aids_taken.append(a)
1322 apps_taken.append(f)
1323 aids_unknown = set(aids_card) - set(aids_taken)
1324 for a in aids_unknown:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001325 print(" unknown: %s (EF.DIR)" % a)
Philipp Maier1e896f32021-03-10 17:02:53 +01001326 else:
Philipp Maier8d8bdef2021-12-01 11:48:27 +01001327 print("warning: EF.DIR seems to be empty!")
1328
1329 # Some card applications may not be registered in EF.DIR, we will actively
1330 # probe for those applications
1331 for f in set(apps_profile) - set(apps_taken):
Bjoern Riemerda57ef12022-01-18 15:38:14 +01001332 try:
1333 data, sw = self.card.select_adf_by_aid(f.aid)
1334 if sw == "9000":
1335 print(" %s: %s" % (f.name, f.aid))
1336 apps_taken.append(f)
1337 except SwMatchError:
1338 pass
Philipp Maier1e896f32021-03-10 17:02:53 +01001339 return apps_taken
1340
Harald Weltea6c0f882022-07-17 14:23:17 +02001341 def reset(self, cmd_app=None) -> Hexstr:
1342 """Perform physical card reset and obtain ATR.
1343 Args:
1344 cmd_app : Command Application State (for unregistering old file commands)
1345 """
1346 # delete all lchan != 0 (basic lchan)
1347 for lchan_nr in self.lchan.keys():
1348 if lchan_nr == 0:
1349 continue
1350 del self.lchan[lchan_nr]
1351 atr = i2h(self.card.reset())
1352 # select MF to reset internal state and to verify card really works
1353 self.lchan[0].select('MF', cmd_app)
1354 self.lchan[0].selected_adf = None
1355 return atr
1356
1357 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1358 """Add a logical channel to the runtime state. You shouldn't call this
1359 directly but always go through RuntimeLchan.add_lchan()."""
1360 if lchan_nr in self.lchan.keys():
1361 raise ValueError('Cannot create already-existing lchan %d' % lchan_nr)
1362 self.lchan[lchan_nr] = RuntimeLchan(lchan_nr, self)
1363 return self.lchan[lchan_nr]
1364
1365 def del_lchan(self, lchan_nr: int):
1366 if lchan_nr in self.lchan.keys():
1367 del self.lchan[lchan_nr]
1368 return True
1369 else:
1370 return False
1371
1372 def get_lchan_by_cla(self, cla) -> Optional['RuntimeLchan']:
1373 lchan_nr = lchan_nr_from_cla(cla)
1374 if lchan_nr in self.lchan.keys():
1375 return self.lchan[lchan_nr]
1376 else:
1377 return None
1378
1379
1380class RuntimeLchan:
1381 """Represent the runtime state of a logical channel with a card."""
1382
1383 def __init__(self, lchan_nr: int, rs: RuntimeState):
1384 self.lchan_nr = lchan_nr
1385 self.rs = rs
1386 self.selected_file = self.rs.mf
1387 self.selected_adf = None
1388 self.selected_file_fcp = None
1389 self.selected_file_fcp_hex = None
1390
1391 def add_lchan(self, lchan_nr: int) -> 'RuntimeLchan':
1392 """Add a new logical channel from the current logical channel. Just affects
1393 internal state, doesn't actually open a channel with the UICC."""
1394 new_lchan = self.rs.add_lchan(lchan_nr)
1395 # See TS 102 221 Table 8.3
1396 if self.lchan_nr != 0:
1397 new_lchan.selected_file = self.get_cwd()
1398 new_lchan.selected_adf = self.selected_adf
1399 return new_lchan
1400
Harald Welte747a9782022-02-13 17:52:28 +01001401 def selected_file_descriptor_byte(self) -> dict:
1402 return self.selected_file_fcp['file_descriptor']['file_descriptor_byte']
1403
1404 def selected_file_shareable(self) -> bool:
1405 return self.selected_file_descriptor_byte()['shareable']
1406
1407 def selected_file_structure(self) -> str:
1408 return self.selected_file_descriptor_byte()['structure']
1409
1410 def selected_file_type(self) -> str:
1411 return self.selected_file_descriptor_byte()['file_type']
1412
1413 def selected_file_num_of_rec(self) -> Optional[int]:
1414 return self.selected_file_fcp['file_descriptor'].get('num_of_rec')
1415
Harald Welteee3501f2021-04-02 13:00:18 +02001416 def get_cwd(self) -> CardDF:
1417 """Obtain the current working directory.
1418
1419 Returns:
1420 CardDF instance
1421 """
Harald Welteb2edd142021-01-08 23:29:35 +01001422 if isinstance(self.selected_file, CardDF):
1423 return self.selected_file
1424 else:
1425 return self.selected_file.parent
1426
Harald Welte5ce35242021-04-02 20:27:05 +02001427 def get_application_df(self) -> Optional[CardADF]:
1428 """Obtain the currently selected application DF (if any).
Harald Welteee3501f2021-04-02 13:00:18 +02001429
1430 Returns:
1431 CardADF() instance or None"""
Harald Welteb2edd142021-01-08 23:29:35 +01001432 # iterate upwards from selected file; check if any is an ADF
1433 node = self.selected_file
1434 while node.parent != node:
1435 if isinstance(node, CardADF):
1436 return node
1437 node = node.parent
1438 return None
1439
Harald Weltec91085e2022-02-10 18:05:45 +01001440 def interpret_sw(self, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001441 """Interpret a given status word relative to the currently selected application
1442 or the underlying card profile.
1443
1444 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001445 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001446
1447 Returns:
1448 Tuple of two strings
1449 """
Harald Welte86fbd392021-04-02 22:13:09 +02001450 res = None
Harald Welte5ce35242021-04-02 20:27:05 +02001451 adf = self.get_application_df()
1452 if adf:
1453 app = adf.application
Harald Welteb2edd142021-01-08 23:29:35 +01001454 # The application either comes with its own interpret_sw
1455 # method or we will use the interpret_sw method from the
1456 # card profile.
Harald Welte5ce35242021-04-02 20:27:05 +02001457 if app and hasattr(app, "interpret_sw"):
Harald Welte86fbd392021-04-02 22:13:09 +02001458 res = app.interpret_sw(sw)
Harald Weltea6c0f882022-07-17 14:23:17 +02001459 return res or self.rs.profile.interpret_sw(sw)
Harald Welteb2edd142021-01-08 23:29:35 +01001460
Harald Weltec91085e2022-02-10 18:05:45 +01001461 def probe_file(self, fid: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001462 """Blindly try to select a file and automatically add a matching file
Harald Weltec91085e2022-02-10 18:05:45 +01001463 object if the file actually exists."""
Philipp Maier63f572d2021-03-09 22:42:47 +01001464 if not is_hex(fid, 4, 4):
Harald Weltec91085e2022-02-10 18:05:45 +01001465 raise ValueError(
1466 "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001467
1468 try:
Harald Weltea6c0f882022-07-17 14:23:17 +02001469 (data, sw) = self.rs.card._scc.select_file(fid)
Philipp Maier63f572d2021-03-09 22:42:47 +01001470 except SwMatchError as swm:
1471 k = self.interpret_sw(swm.sw_actual)
1472 if not k:
1473 raise(swm)
1474 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
1475
1476 select_resp = self.selected_file.decode_select_response(data)
Harald Welte747a9782022-02-13 17:52:28 +01001477 if (select_resp['file_descriptor']['file_descriptor_byte']['file_type'] == 'df'):
Harald Weltec91085e2022-02-10 18:05:45 +01001478 f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
1479 desc="dedicated file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001480 else:
Harald Welte747a9782022-02-13 17:52:28 +01001481 if (select_resp['file_descriptor']['file_descriptor_byte']['structure'] == 'transparent'):
Harald Weltec91085e2022-02-10 18:05:45 +01001482 f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1483 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001484 else:
Harald Weltec91085e2022-02-10 18:05:45 +01001485 f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
1486 desc="elementary file, manually added at runtime")
Philipp Maier63f572d2021-03-09 22:42:47 +01001487
1488 self.selected_file.add_files([f])
1489 self.selected_file = f
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001490 return select_resp, data
Philipp Maier63f572d2021-03-09 22:42:47 +01001491
Harald Welteaceb2a52022-02-12 21:41:59 +01001492 def _select_pre(self, cmd_app):
1493 # unregister commands of old file
1494 if cmd_app and self.selected_file.shell_commands:
1495 for c in self.selected_file.shell_commands:
1496 cmd_app.unregister_command_set(c)
1497
1498 def _select_post(self, cmd_app):
1499 # register commands of new file
1500 if cmd_app and self.selected_file.shell_commands:
1501 for c in self.selected_file.shell_commands:
1502 cmd_app.register_command_set(c)
1503
1504 def select_file(self, file: CardFile, cmd_app=None):
1505 """Select a file (EF, DF, ADF, MF, ...).
1506
1507 Args:
1508 file : CardFile [or derived class] instance
1509 cmd_app : Command Application State (for unregistering old file commands)
1510 """
1511 # we need to find a path from our self.selected_file to the destination
1512 inter_path = self.selected_file.build_select_path_to(file)
1513 if not inter_path:
1514 raise RuntimeError('Cannot determine path from %s to %s' % (self.selected_file, file))
1515
1516 self._select_pre(cmd_app)
1517
1518 for p in inter_path:
1519 try:
1520 if isinstance(p, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001521 (data, sw) = self.rs.card.select_adf_by_aid(p.aid)
Harald Welte46a7a3f2022-07-16 11:47:47 +02001522 self.selected_adf = p
Harald Welteaceb2a52022-02-12 21:41:59 +01001523 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001524 (data, sw) = self.rs.card._scc.select_file(p.fid)
Harald Welteaceb2a52022-02-12 21:41:59 +01001525 self.selected_file = p
1526 except SwMatchError as swm:
1527 self._select_post(cmd_app)
1528 raise(swm)
1529
1530 self._select_post(cmd_app)
1531
Harald Weltec91085e2022-02-10 18:05:45 +01001532 def select(self, name: str, cmd_app=None):
Harald Welteee3501f2021-04-02 13:00:18 +02001533 """Select a file (EF, DF, ADF, MF, ...).
1534
1535 Args:
1536 name : Name of file to select
1537 cmd_app : Command Application State (for unregistering old file commands)
1538 """
Harald Welteee670bc2022-02-13 15:10:15 +01001539 # handling of entire paths with multiple directories/elements
1540 if '/' in name:
1541 prev_sel_file = self.selected_file
1542 pathlist = name.split('/')
1543 # treat /DF.GSM/foo like MF/DF.GSM/foo
1544 if pathlist[0] == '':
1545 pathlist[0] = 'MF'
1546 try:
1547 for p in pathlist:
1548 self.select(p, cmd_app)
1549 return
1550 except Exception as e:
1551 # if any intermediate step fails, go back to where we were
1552 self.select_file(prev_sel_file, cmd_app)
1553 raise e
1554
Harald Welteb2edd142021-01-08 23:29:35 +01001555 sels = self.selected_file.get_selectables()
Philipp Maier7744b6e2021-03-11 14:29:37 +01001556 if is_hex(name):
1557 name = name.lower()
Philipp Maier63f572d2021-03-09 22:42:47 +01001558
Harald Welteaceb2a52022-02-12 21:41:59 +01001559 self._select_pre(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001560
Harald Welteb2edd142021-01-08 23:29:35 +01001561 if name in sels:
1562 f = sels[name]
Harald Welteb2edd142021-01-08 23:29:35 +01001563 try:
1564 if isinstance(f, CardADF):
Harald Weltea6c0f882022-07-17 14:23:17 +02001565 (data, sw) = self.rs.card.select_adf_by_aid(f.aid)
Harald Welteb2edd142021-01-08 23:29:35 +01001566 else:
Harald Weltea6c0f882022-07-17 14:23:17 +02001567 (data, sw) = self.rs.card._scc.select_file(f.fid)
Harald Welteb2edd142021-01-08 23:29:35 +01001568 self.selected_file = f
1569 except SwMatchError as swm:
1570 k = self.interpret_sw(swm.sw_actual)
1571 if not k:
1572 raise(swm)
1573 raise RuntimeError("%s: %s - %s" % (swm.sw_actual, k[0], k[1]))
Philipp Maier63f572d2021-03-09 22:42:47 +01001574 select_resp = f.decode_select_response(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001575 else:
Philipp Maier6b8eedc2022-06-01 18:10:04 +02001576 (select_resp, data) = self.probe_file(name, cmd_app)
1577
Harald Welte2bb17f32022-02-15 15:41:55 +01001578 # store the raw + decoded FCP for later reference
1579 self.selected_file_fcp_hex = data
Harald Welte850b72a2021-04-07 09:33:03 +02001580 self.selected_file_fcp = select_resp
Philipp Maier63f572d2021-03-09 22:42:47 +01001581
Harald Welteaceb2a52022-02-12 21:41:59 +01001582 self._select_post(cmd_app)
Philipp Maier63f572d2021-03-09 22:42:47 +01001583 return select_resp
Harald Welteb2edd142021-01-08 23:29:35 +01001584
Harald Welte34b05d32021-05-25 22:03:13 +02001585 def status(self):
1586 """Request STATUS (current selected file FCP) from card."""
Harald Weltea6c0f882022-07-17 14:23:17 +02001587 (data, sw) = self.rs.card._scc.status()
Harald Welte34b05d32021-05-25 22:03:13 +02001588 return self.selected_file.decode_select_response(data)
1589
Harald Welte3c9b7842021-10-19 21:44:24 +02001590 def get_file_for_selectable(self, name: str):
1591 sels = self.selected_file.get_selectables()
1592 return sels[name]
1593
Harald Weltec91085e2022-02-10 18:05:45 +01001594 def activate_file(self, name: str):
Harald Welte485692b2021-05-25 22:21:44 +02001595 """Request ACTIVATE FILE of specified file."""
1596 sels = self.selected_file.get_selectables()
1597 f = sels[name]
Harald Weltea6c0f882022-07-17 14:23:17 +02001598 data, sw = self.rs.card._scc.activate_file(f.fid)
Harald Welte485692b2021-05-25 22:21:44 +02001599 return data, sw
1600
Harald Weltec91085e2022-02-10 18:05:45 +01001601 def read_binary(self, length: int = None, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001602 """Read [part of] a transparent EF binary data.
1603
1604 Args:
1605 length : Amount of data to read (None: as much as possible)
1606 offset : Offset into the file from which to read 'length' bytes
1607 Returns:
1608 binary data read from the file
1609 """
Harald Welteb2edd142021-01-08 23:29:35 +01001610 if not isinstance(self.selected_file, TransparentEF):
1611 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001612 return self.rs.card._scc.read_binary(self.selected_file.fid, length, offset)
Harald Welteb2edd142021-01-08 23:29:35 +01001613
Harald Welte2d4a64b2021-04-03 09:01:24 +02001614 def read_binary_dec(self) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001615 """Read [part of] a transparent EF binary data and decode it.
1616
1617 Args:
1618 length : Amount of data to read (None: as much as possible)
1619 offset : Offset into the file from which to read 'length' bytes
1620 Returns:
1621 abstract decode data read from the file
1622 """
Harald Welteb2edd142021-01-08 23:29:35 +01001623 (data, sw) = self.read_binary()
1624 dec_data = self.selected_file.decode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001625 return (dec_data, sw)
1626
Harald Weltec91085e2022-02-10 18:05:45 +01001627 def update_binary(self, data_hex: str, offset: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001628 """Update transparent EF binary data.
1629
1630 Args:
1631 data_hex : hex string of data to be written
1632 offset : Offset into the file from which to write 'data_hex'
1633 """
Harald Welteb2edd142021-01-08 23:29:35 +01001634 if not isinstance(self.selected_file, TransparentEF):
1635 raise TypeError("Only works with TransparentEF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001636 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 +01001637
Harald Weltec91085e2022-02-10 18:05:45 +01001638 def update_binary_dec(self, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001639 """Update transparent EF from abstract data. Encodes the data to binary and
1640 then updates the EF with it.
1641
1642 Args:
1643 data : abstract data which is to be encoded and written
1644 """
Harald Welteb2edd142021-01-08 23:29:35 +01001645 data_hex = self.selected_file.encode_hex(data)
Harald Welteb2edd142021-01-08 23:29:35 +01001646 return self.update_binary(data_hex)
1647
Harald Weltec91085e2022-02-10 18:05:45 +01001648 def read_record(self, rec_nr: int = 0):
Harald Welteee3501f2021-04-02 13:00:18 +02001649 """Read a record as binary data.
1650
1651 Args:
1652 rec_nr : Record number to read
1653 Returns:
1654 hex string of binary data contained in record
1655 """
Harald Welteb2edd142021-01-08 23:29:35 +01001656 if not isinstance(self.selected_file, LinFixedEF):
1657 raise TypeError("Only works with Linear Fixed EF")
1658 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001659 return self.rs.card._scc.read_record(self.selected_file.fid, rec_nr)
Harald Welteb2edd142021-01-08 23:29:35 +01001660
Harald Weltec91085e2022-02-10 18:05:45 +01001661 def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
Harald Welteee3501f2021-04-02 13:00:18 +02001662 """Read a record and decode it to abstract data.
1663
1664 Args:
1665 rec_nr : Record number to read
1666 Returns:
1667 abstract data contained in record
1668 """
Harald Welteb2edd142021-01-08 23:29:35 +01001669 (data, sw) = self.read_record(rec_nr)
1670 return (self.selected_file.decode_record_hex(data), sw)
1671
Harald Weltec91085e2022-02-10 18:05:45 +01001672 def update_record(self, rec_nr: int, data_hex: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001673 """Update a record with given binary data
1674
1675 Args:
1676 rec_nr : Record number to read
1677 data_hex : Hex string binary data to be written
1678 """
Harald Welteb2edd142021-01-08 23:29:35 +01001679 if not isinstance(self.selected_file, LinFixedEF):
1680 raise TypeError("Only works with Linear Fixed EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001681 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 +01001682
Harald Weltec91085e2022-02-10 18:05:45 +01001683 def update_record_dec(self, rec_nr: int, data: dict):
Harald Welteee3501f2021-04-02 13:00:18 +02001684 """Update a record with given abstract data. Will encode abstract to binary data
1685 and then write it to the given record on the card.
1686
1687 Args:
1688 rec_nr : Record number to read
1689 data_hex : Abstract data to be written
1690 """
Harald Welte1e456572021-04-02 17:16:30 +02001691 data_hex = self.selected_file.encode_record_hex(data)
1692 return self.update_record(rec_nr, data_hex)
Harald Welteb2edd142021-01-08 23:29:35 +01001693
Harald Weltec91085e2022-02-10 18:05:45 +01001694 def retrieve_data(self, tag: int = 0):
Harald Welte917d98c2021-04-21 11:51:25 +02001695 """Read a DO/TLV as binary data.
1696
1697 Args:
1698 tag : Tag of TLV/DO to read
1699 Returns:
1700 hex string of full BER-TLV DO including Tag and Length
1701 """
1702 if not isinstance(self.selected_file, BerTlvEF):
1703 raise TypeError("Only works with BER-TLV EF")
1704 # returns a string of hex nibbles
Harald Weltea6c0f882022-07-17 14:23:17 +02001705 return self.rs.card._scc.retrieve_data(self.selected_file.fid, tag)
Harald Welte917d98c2021-04-21 11:51:25 +02001706
1707 def retrieve_tags(self):
1708 """Retrieve tags available on BER-TLV EF.
1709
1710 Returns:
1711 list of integer tags contained in EF
1712 """
1713 if not isinstance(self.selected_file, BerTlvEF):
1714 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001715 data, sw = self.rs.card._scc.retrieve_data(self.selected_file.fid, 0x5c)
Harald Weltec1475302021-05-21 21:47:55 +02001716 tag, length, value, remainder = bertlv_parse_one(h2b(data))
Harald Welte917d98c2021-04-21 11:51:25 +02001717 return list(value)
1718
Harald Weltec91085e2022-02-10 18:05:45 +01001719 def set_data(self, tag: int, data_hex: str):
Harald Welte917d98c2021-04-21 11:51:25 +02001720 """Update a TLV/DO with given binary data
1721
1722 Args:
1723 tag : Tag of TLV/DO to be written
1724 data_hex : Hex string binary data to be written (value portion)
1725 """
1726 if not isinstance(self.selected_file, BerTlvEF):
1727 raise TypeError("Only works with BER-TLV EF")
Harald Weltea6c0f882022-07-17 14:23:17 +02001728 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 +02001729
Philipp Maier5d698e52021-09-16 13:18:01 +02001730 def unregister_cmds(self, cmd_app=None):
1731 """Unregister all file specific commands."""
1732 if cmd_app and self.selected_file.shell_commands:
1733 for c in self.selected_file.shell_commands:
1734 cmd_app.unregister_command_set(c)
Harald Welte917d98c2021-04-21 11:51:25 +02001735
Harald Welteb2edd142021-01-08 23:29:35 +01001736
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001737class FileData:
Harald Welteb2edd142021-01-08 23:29:35 +01001738 """Represent the runtime, on-card data."""
Harald Weltec91085e2022-02-10 18:05:45 +01001739
Harald Welteb2edd142021-01-08 23:29:35 +01001740 def __init__(self, fdesc):
1741 self.desc = fdesc
1742 self.fcp = None
1743
1744
Harald Weltec91085e2022-02-10 18:05:45 +01001745def interpret_sw(sw_data: dict, sw: str):
Harald Welteee3501f2021-04-02 13:00:18 +02001746 """Interpret a given status word.
1747
1748 Args:
1749 sw_data : Hierarchical dict of status word matches
1750 sw : status word to match (string of 4 hex digits)
1751 Returns:
1752 tuple of two strings (class_string, description)
1753 """
Harald Welteb2edd142021-01-08 23:29:35 +01001754 for class_str, swdict in sw_data.items():
1755 # first try direct match
1756 if sw in swdict:
1757 return (class_str, swdict[sw])
1758 # next try wildcard matches
1759 for pattern, descr in swdict.items():
1760 if sw_match(sw, pattern):
1761 return (class_str, descr)
1762 return None
1763
Harald Weltec91085e2022-02-10 18:05:45 +01001764
Vadim Yanitskiy04b5d9d2022-07-07 03:05:30 +07001765class CardApplication:
Harald Welteb2edd142021-01-08 23:29:35 +01001766 """A card application is represented by an ADF (with contained hierarchy) and optionally
1767 some SW definitions."""
Harald Weltec91085e2022-02-10 18:05:45 +01001768
1769 def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
Harald Welteee3501f2021-04-02 13:00:18 +02001770 """
1771 Args:
1772 adf : ADF name
1773 sw : Dict of status word conversions
1774 """
Harald Welteb2edd142021-01-08 23:29:35 +01001775 self.name = name
1776 self.adf = adf
Vadim Yanitskiy98f872b2021-03-27 01:25:46 +01001777 self.sw = sw or dict()
Harald Welte5ce35242021-04-02 20:27:05 +02001778 # back-reference from ADF to Applicaiton
1779 if self.adf:
1780 self.aid = aid or self.adf.aid
1781 self.adf.application = self
1782 else:
1783 self.aid = aid
Harald Welteb2edd142021-01-08 23:29:35 +01001784
1785 def __str__(self):
1786 return "APP(%s)" % (self.name)
1787
1788 def interpret_sw(self, sw):
Harald Welteee3501f2021-04-02 13:00:18 +02001789 """Interpret a given status word within the application.
1790
1791 Args:
Harald Weltec9cdce32021-04-11 10:28:28 +02001792 sw : Status word as string of 4 hex digits
Harald Welteee3501f2021-04-02 13:00:18 +02001793
1794 Returns:
1795 Tuple of two strings
1796 """
Harald Welteb2edd142021-01-08 23:29:35 +01001797 return interpret_sw(self.sw, sw)
1798
Harald Weltef44256c2021-10-14 15:53:39 +02001799
1800class CardModel(abc.ABC):
Harald Welte4c1dca02021-10-14 17:48:25 +02001801 """A specific card model, typically having some additional vendor-specific files. All
1802 you need to do is to define a sub-class with a list of ATRs or an overridden match
1803 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001804 _atrs = []
1805
1806 @classmethod
1807 @abc.abstractmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001808 def add_files(cls, rs: RuntimeState):
Harald Weltef44256c2021-10-14 15:53:39 +02001809 """Add model specific files to given RuntimeState."""
1810
1811 @classmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001812 def match(cls, scc: SimCardCommands) -> bool:
Harald Weltef44256c2021-10-14 15:53:39 +02001813 """Test if given card matches this model."""
1814 card_atr = scc.get_atr()
1815 for atr in cls._atrs:
1816 atr_bin = toBytes(atr)
1817 if atr_bin == card_atr:
1818 print("Detected CardModel:", cls.__name__)
1819 return True
1820 return False
1821
1822 @staticmethod
Harald Weltec91085e2022-02-10 18:05:45 +01001823 def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
Harald Welte4c1dca02021-10-14 17:48:25 +02001824 """Check if any of the CardModel sub-classes 'match' the currently inserted card
1825 (by ATR or overriding the 'match' method). If so, call their 'add_files'
1826 method."""
Harald Weltef44256c2021-10-14 15:53:39 +02001827 for m in CardModel.__subclasses__():
1828 if m.match(scc):
1829 m.add_files(rs)