blob: a82d56f905703a48fe84bdea2137f9ba9c31a4a4 [file] [log] [blame]
Harald Welteb2edd142021-01-08 23:29:35 +01001#!/usr/bin/env python3
2
3# Interactive shell for working with SIM / UICC / USIM / ISIM cards
4#
5# (C) 2021 by Harald Welte <laforge@osmocom.org>
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20from typing import List
21
22import json
Philipp Maier5d698e52021-09-16 13:18:01 +020023import traceback
Harald Welteb2edd142021-01-08 23:29:35 +010024
25import cmd2
26from cmd2 import style, fg, bg
27from cmd2 import CommandSet, with_default_category, with_argparser
28import argparse
29
30import os
31import sys
Philipp Maier2b11c322021-03-17 12:37:39 +010032from pathlib import Path
Philipp Maier76667642021-09-22 16:53:22 +020033from io import StringIO
Harald Welteb2edd142021-01-08 23:29:35 +010034
Robert Falkenberg9d16fbc2021-04-12 11:43:22 +020035from pySim.ts_51_011 import EF, DF, EF_SST_map
Harald Welteb2edd142021-01-08 23:29:35 +010036from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map
37from pySim.ts_31_103 import EF_IST_map, EF_ISIM_ADF_map
38
39from pySim.exceptions import *
40from pySim.commands import SimCardCommands
Harald Welte28c24312021-04-11 12:19:36 +020041from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args
Philipp Maierbb73e512021-05-05 16:14:00 +020042from pySim.cards import card_detect, SimCard
Harald Welte917d98c2021-04-21 11:51:25 +020043from pySim.utils import h2b, swap_nibbles, rpad, b2h, h2s, JsonEncoder, bertlv_parse_one
Philipp Maier80ce71f2021-04-19 21:24:23 +020044from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex, boxed_heading_str
Philipp Maier76667642021-09-22 16:53:22 +020045from pySim.card_handler import CardHandler, CardHandlerAuto
Harald Welteb2edd142021-01-08 23:29:35 +010046
Harald Weltef44256c2021-10-14 15:53:39 +020047from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF, CardModel
Philipp Maiera028c7d2021-11-08 16:12:03 +010048from pySim.profile import CardProfile
Harald Welteb2edd142021-01-08 23:29:35 +010049from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
50from pySim.ts_102_221 import CardProfileUICC
Philipp Maiera028c7d2021-11-08 16:12:03 +010051from pySim.ts_102_221 import CardProfileUICCSIM
Harald Welte5ce35242021-04-02 20:27:05 +020052from pySim.ts_31_102 import CardApplicationUSIM
53from pySim.ts_31_103 import CardApplicationISIM
Harald Welte95ce6b12021-10-20 18:40:54 +020054from pySim.ara_m import CardApplicationARAM
Harald Welte2a33ad22021-10-20 10:14:18 +020055from pySim.gsm_r import DF_EIRENE
Harald Welteb2edd142021-01-08 23:29:35 +010056
Harald Welte4c1dca02021-10-14 17:48:25 +020057# we need to import this module so that the SysmocomSJA2 sub-class of
58# CardModel is created, which will add the ATR-based matching and
59# calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models
Harald Weltef44256c2021-10-14 15:53:39 +020060import pySim.sysmocom_sja2
61
Harald Welte4442b3d2021-04-03 09:00:16 +020062from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010063
Harald Weltec91085e2022-02-10 18:05:45 +010064
Philipp Maierea95c392021-09-16 13:10:19 +020065def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010066 """
67 Detect card in reader and setup card profile and runtime state. This
68 function must be called at least once on startup. The card and runtime
69 state object (rs) is required for all pySim-shell commands.
70 """
Philipp Maierea95c392021-09-16 13:10:19 +020071
Harald Weltec91085e2022-02-10 18:05:45 +010072 # Wait up to three seconds for a card in reader and try to detect
73 # the card type.
74 print("Waiting for card...")
75 try:
76 sl.wait_for_card(3)
77 except NoCardError:
78 print("No card detected!")
79 return None, None
80 except:
81 print("Card not readable!")
82 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020083
Harald Weltec91085e2022-02-10 18:05:45 +010084 card = card_detect("auto", scc)
85 if card is None:
86 print("Warning: Could not detect card type - assuming a generic card type...")
87 card = SimCard(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +010088
Harald Weltec91085e2022-02-10 18:05:45 +010089 profile = CardProfile.pick(scc)
90 if profile is None:
91 print("Unsupported card type!")
92 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020093
Harald Weltec91085e2022-02-10 18:05:45 +010094 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +010095
Harald Weltec91085e2022-02-10 18:05:45 +010096 # FIXME: This shouln't be here, the profile should add the applications,
97 # however, we cannot simply put his into ts_102_221.py since we would
98 # have to e.g. import CardApplicationUSIM from ts_31_102.py, which already
99 # imports from ts_102_221.py. This means we will end up with a circular
100 # import, which needs to be resolved first.
101 if isinstance(profile, CardProfileUICC):
102 profile.add_application(CardApplicationUSIM())
103 profile.add_application(CardApplicationISIM())
104 profile.add_application(CardApplicationARAM())
Philipp Maiera028c7d2021-11-08 16:12:03 +0100105
Harald Weltec91085e2022-02-10 18:05:45 +0100106 # Create runtime state with card profile
107 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200108
Harald Weltec91085e2022-02-10 18:05:45 +0100109 # FIXME: This is an GSM-R related file, it needs to be added throughout,
110 # the profile. At the moment we add it for all cards, this won't hurt,
111 # but regular SIM and UICC will not have it and fail to select it.
112 rs.mf.add_file(DF_EIRENE())
Philipp Maierea95c392021-09-16 13:10:19 +0200113
Harald Weltec91085e2022-02-10 18:05:45 +0100114 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200115
Harald Weltec91085e2022-02-10 18:05:45 +0100116 # inform the transport that we can do context-specific SW interpretation
117 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200118
Harald Weltec91085e2022-02-10 18:05:45 +0100119 return rs, card
120
Philipp Maier2b11c322021-03-17 12:37:39 +0100121
Harald Welteb2edd142021-01-08 23:29:35 +0100122class PysimApp(cmd2.Cmd):
Harald Weltec91085e2022-02-10 18:05:45 +0100123 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier76667642021-09-22 16:53:22 +0200124
Harald Weltec91085e2022-02-10 18:05:45 +0100125 def __init__(self, card, rs, sl, ch, script=None):
126 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
127 use_ipython=True, auto_load_commands=False, startup_script=script)
128 self.intro = style('Welcome to pySim-shell!', fg=fg.red)
129 self.default_category = 'pySim-shell built-in commands'
130 self.card = None
131 self.rs = None
132 self.py_locals = {'card': self.card, 'rs': self.rs}
133 self.sl = sl
134 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200135
Harald Weltec91085e2022-02-10 18:05:45 +0100136 self.numeric_path = False
137 self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
138 onchange_cb=self._onchange_numeric_path))
139 self.conserve_write = True
140 self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
141 onchange_cb=self._onchange_conserve_write))
142 self.json_pretty_print = True
143 self.add_settable(cmd2.Settable('json_pretty_print',
144 bool, 'Pretty-Print JSON output'))
145 self.apdu_trace = False
146 self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
147 onchange_cb=self._onchange_apdu_trace))
Philipp Maier5d698e52021-09-16 13:18:01 +0200148
Harald Weltec91085e2022-02-10 18:05:45 +0100149 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200150
Harald Weltec91085e2022-02-10 18:05:45 +0100151 def equip(self, card, rs):
152 """
153 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
154 and commands to enable card operations.
155 """
Philipp Maier76667642021-09-22 16:53:22 +0200156
Harald Weltec91085e2022-02-10 18:05:45 +0100157 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200158
Harald Weltec91085e2022-02-10 18:05:45 +0100159 # Unequip everything from pySim-shell that would not work in unequipped state
160 if self.rs:
161 self.rs.unregister_cmds(self)
162 for cmds in [Iso7816Commands, PySimCommands]:
163 cmd_set = self.find_commandsets(cmds)
164 if cmd_set:
165 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200166
Harald Weltec91085e2022-02-10 18:05:45 +0100167 self.card = card
168 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200169
Harald Weltec91085e2022-02-10 18:05:45 +0100170 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
171 # needed to operate on cards.
172 if self.card and self.rs:
173 self._onchange_conserve_write(
174 'conserve_write', False, self.conserve_write)
175 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
176 self.register_command_set(Iso7816Commands())
177 self.register_command_set(PySimCommands())
178 self.iccid, sw = self.card.read_iccid()
179 rs.select('MF', self)
180 rc = True
181 else:
182 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200183
Harald Weltec91085e2022-02-10 18:05:45 +0100184 self.update_prompt()
185 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100186
Harald Weltec91085e2022-02-10 18:05:45 +0100187 def poutput_json(self, data, force_no_pretty=False):
188 """like cmd2.poutput() but for a JSON serializable dict."""
189 if force_no_pretty or self.json_pretty_print == False:
190 output = json.dumps(data, cls=JsonEncoder)
191 else:
192 output = json.dumps(data, cls=JsonEncoder, indent=4)
193 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100194
Harald Weltec91085e2022-02-10 18:05:45 +0100195 def _onchange_numeric_path(self, param_name, old, new):
196 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100197
Harald Weltec91085e2022-02-10 18:05:45 +0100198 def _onchange_conserve_write(self, param_name, old, new):
199 if self.rs:
200 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200201
Harald Weltec91085e2022-02-10 18:05:45 +0100202 def _onchange_apdu_trace(self, param_name, old, new):
203 if self.card:
204 if new == True:
205 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
206 else:
207 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200208
Harald Weltec91085e2022-02-10 18:05:45 +0100209 class Cmd2ApduTracer(ApduTracer):
210 def __init__(self, cmd2_app):
211 self.cmd2 = app
Harald Welte7829d8a2021-04-10 11:28:53 +0200212
Harald Weltec91085e2022-02-10 18:05:45 +0100213 def trace_response(self, cmd, sw, resp):
214 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
215 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100216
Harald Weltec91085e2022-02-10 18:05:45 +0100217 def update_prompt(self):
218 if self.rs:
219 path_list = self.rs.selected_file.fully_qualified_path(
220 not self.numeric_path)
221 self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
222 else:
223 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100224
Harald Weltec91085e2022-02-10 18:05:45 +0100225 @cmd2.with_category(CUSTOM_CATEGORY)
226 def do_intro(self, _):
227 """Display the intro banner"""
228 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100229
Harald Weltec91085e2022-02-10 18:05:45 +0100230 def do_eof(self, _: argparse.Namespace) -> bool:
231 self.poutput("")
232 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200233
Harald Weltec91085e2022-02-10 18:05:45 +0100234 @cmd2.with_category(CUSTOM_CATEGORY)
235 def do_equip(self, opts):
236 """Equip pySim-shell with card"""
237 rs, card = init_card(sl)
238 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200239
Harald Weltec91085e2022-02-10 18:05:45 +0100240 class InterceptStderr(list):
241 def __init__(self):
242 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200243
Harald Weltec91085e2022-02-10 18:05:45 +0100244 def __enter__(self):
245 self._stringio_stderr = StringIO()
246 sys.stderr = self._stringio_stderr
247 return self
Philipp Maier76667642021-09-22 16:53:22 +0200248
Harald Weltec91085e2022-02-10 18:05:45 +0100249 def __exit__(self, *args):
250 self.stderr = self._stringio_stderr.getvalue().strip()
251 del self._stringio_stderr
252 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200253
Harald Weltec91085e2022-02-10 18:05:45 +0100254 def _show_failure_sign(self):
255 self.poutput(style(" +-------------+", fg=fg.bright_red))
256 self.poutput(style(" + ## ## +", fg=fg.bright_red))
257 self.poutput(style(" + ## ## +", fg=fg.bright_red))
258 self.poutput(style(" + ### +", fg=fg.bright_red))
259 self.poutput(style(" + ## ## +", fg=fg.bright_red))
260 self.poutput(style(" + ## ## +", fg=fg.bright_red))
261 self.poutput(style(" +-------------+", fg=fg.bright_red))
262 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200263
Harald Weltec91085e2022-02-10 18:05:45 +0100264 def _show_success_sign(self):
265 self.poutput(style(" +-------------+", fg=fg.bright_green))
266 self.poutput(style(" + ## +", fg=fg.bright_green))
267 self.poutput(style(" + ## +", fg=fg.bright_green))
268 self.poutput(style(" + # ## +", fg=fg.bright_green))
269 self.poutput(style(" + ## # +", fg=fg.bright_green))
270 self.poutput(style(" + ## +", fg=fg.bright_green))
271 self.poutput(style(" +-------------+", fg=fg.bright_green))
272 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200273
Harald Weltec91085e2022-02-10 18:05:45 +0100274 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200275
Harald Weltec91085e2022-02-10 18:05:45 +0100276 # Early phase of card initialzation (this part may fail with an exception)
277 try:
278 rs, card = init_card(self.sl)
279 rc = self.equip(card, rs)
280 except:
281 self.poutput("")
282 self.poutput("Card initialization failed with an exception:")
283 self.poutput("---------------------8<---------------------")
284 traceback.print_exc()
285 self.poutput("---------------------8<---------------------")
286 self.poutput("")
287 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200288
Harald Weltec91085e2022-02-10 18:05:45 +0100289 # Actual card processing step. This part should never fail with an exception since the cmd2
290 # do_run_script method will catch any exception that might occur during script execution.
291 if rc:
292 self.poutput("")
293 self.poutput("Transcript stdout:")
294 self.poutput("---------------------8<---------------------")
295 with self.InterceptStderr() as logged:
296 self.do_run_script(script_path)
297 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200298
Harald Weltec91085e2022-02-10 18:05:45 +0100299 self.poutput("")
300 self.poutput("Transcript stderr:")
301 if logged.stderr:
302 self.poutput("---------------------8<---------------------")
303 self.poutput(logged.stderr)
304 self.poutput("---------------------8<---------------------")
305 else:
306 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200307
Harald Weltec91085e2022-02-10 18:05:45 +0100308 # Check for exceptions
309 self.poutput("")
310 if "EXCEPTION of type" not in logged.stderr:
311 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200312
Harald Weltec91085e2022-02-10 18:05:45 +0100313 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200314
Harald Weltec91085e2022-02-10 18:05:45 +0100315 bulk_script_parser = argparse.ArgumentParser()
316 bulk_script_parser.add_argument(
317 'script_path', help="path to the script file")
318 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
319 action='store_true')
320 bulk_script_parser.add_argument('--tries', type=int, default=2,
321 help='how many tries before trying the next card')
322 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
323 help='commandline to execute when card handling has stopped')
324 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
325 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200326
Harald Weltec91085e2022-02-10 18:05:45 +0100327 @cmd2.with_argparser(bulk_script_parser)
328 @cmd2.with_category(CUSTOM_CATEGORY)
329 def do_bulk_script(self, opts):
330 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200331
Harald Weltec91085e2022-02-10 18:05:45 +0100332 # Make sure that the script file exists and that it is readable.
333 if not os.access(opts.script_path, os.R_OK):
334 self.poutput("Invalid script file!")
335 return
Philipp Maier76667642021-09-22 16:53:22 +0200336
Harald Weltec91085e2022-02-10 18:05:45 +0100337 success_count = 0
338 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200339
Harald Weltec91085e2022-02-10 18:05:45 +0100340 first = True
341 while 1:
342 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
343 # The ratinale is: There may be a problem with the device, we do want to prevent that
344 # all remaining cards are fired to the error bin. This is only relevant for situations
345 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200346
Harald Weltec91085e2022-02-10 18:05:45 +0100347 try:
348 # In case of failure, try multiple times.
349 for i in range(opts.tries):
350 # fetch card into reader bay
351 ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200352
Harald Weltec91085e2022-02-10 18:05:45 +0100353 # if necessary execute an action before we start processing the card
354 if(opts.pre_card_action):
355 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200356
Harald Weltec91085e2022-02-10 18:05:45 +0100357 # process the card
358 rc = self._process_card(first, opts.script_path)
359 if rc == 0:
360 success_count = success_count + 1
361 self._show_success_sign()
362 self.poutput("Statistics: success :%i, failure: %i" % (
363 success_count, fail_count))
364 break
365 else:
366 fail_count = fail_count + 1
367 self._show_failure_sign()
368 self.poutput("Statistics: success :%i, failure: %i" % (
369 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200370
Harald Weltec91085e2022-02-10 18:05:45 +0100371 # Depending on success or failure, the card goes either in the "error" bin or in the
372 # "done" bin.
373 if rc < 0:
374 ch.error()
375 else:
376 ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200377
Harald Weltec91085e2022-02-10 18:05:45 +0100378 # In most cases it is possible to proceed with the next card, but the
379 # user may decide to halt immediately when an error occurs
380 if opts.halt_on_error and rc < 0:
381 return
Philipp Maier76667642021-09-22 16:53:22 +0200382
Harald Weltec91085e2022-02-10 18:05:45 +0100383 except (KeyboardInterrupt):
384 self.poutput("")
385 self.poutput("Terminated by user!")
386 return
387 except (SystemExit):
388 # When all cards are processed the card handler device will throw a SystemExit
389 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
390 # The user has the option to execute some action to make aware that the card handler
391 # needs service.
392 if(opts.on_stop_action):
393 os.system(opts.on_stop_action)
394 return
395 except:
396 self.poutput("")
397 self.poutput("Card handling failed with an exception:")
398 self.poutput("---------------------8<---------------------")
399 traceback.print_exc()
400 self.poutput("---------------------8<---------------------")
401 self.poutput("")
402 fail_count = fail_count + 1
403 self._show_failure_sign()
404 self.poutput("Statistics: success :%i, failure: %i" %
405 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200406
Harald Weltec91085e2022-02-10 18:05:45 +0100407 first = False
408
409 echo_parser = argparse.ArgumentParser()
410 echo_parser.add_argument('string', help="string to echo on the shell")
411
412 @cmd2.with_argparser(echo_parser)
413 @cmd2.with_category(CUSTOM_CATEGORY)
414 def do_echo(self, opts):
415 """Echo (print) a string on the console"""
416 self.poutput(opts.string)
417
Harald Welteb2edd142021-01-08 23:29:35 +0100418
Harald Welte31d2cf02021-04-03 10:47:29 +0200419@with_default_category('pySim Commands')
420class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100421 def __init__(self):
422 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100423
Harald Weltec91085e2022-02-10 18:05:45 +0100424 dir_parser = argparse.ArgumentParser()
425 dir_parser.add_argument(
426 '--fids', help='Show file identifiers', action='store_true')
427 dir_parser.add_argument(
428 '--names', help='Show file names', action='store_true')
429 dir_parser.add_argument(
430 '--apps', help='Show applications', action='store_true')
431 dir_parser.add_argument(
432 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100433
Harald Weltec91085e2022-02-10 18:05:45 +0100434 @cmd2.with_argparser(dir_parser)
435 def do_dir(self, opts):
436 """Show a listing of files available in currently selected DF or MF"""
437 if opts.all:
438 flags = []
439 elif opts.fids or opts.names or opts.apps:
440 flags = ['PARENT', 'SELF']
441 if opts.fids:
442 flags += ['FIDS', 'AIDS']
443 if opts.names:
444 flags += ['FNAMES', 'ANAMES']
445 if opts.apps:
446 flags += ['ANAMES', 'AIDS']
447 else:
448 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
449 selectables = list(
450 self._cmd.rs.selected_file.get_selectable_names(flags=flags))
451 directory_str = tabulate_str_list(
452 selectables, width=79, hspace=2, lspace=1, align_left=True)
453 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
454 self._cmd.poutput('/'.join(path_list))
455 path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
456 self._cmd.poutput('/'.join(path_list))
457 self._cmd.poutput(directory_str)
458 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100459
Harald Welte08b11ab2022-02-10 18:56:41 +0100460 def walk(self, indent=0, action=None, context=None, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100461 """Recursively walk through the file system, starting at the currently selected DF"""
462 files = self._cmd.rs.selected_file.get_selectables(
463 flags=['FNAMES', 'ANAMES'])
464 for f in files:
465 if not action:
466 output_str = " " * indent + str(f) + (" " * 250)
467 output_str = output_str[0:25]
468 if isinstance(files[f], CardADF):
469 output_str += " " + str(files[f].aid)
470 else:
471 output_str += " " + str(files[f].fid)
472 output_str += " " + str(files[f].desc)
473 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200474
Harald Weltec91085e2022-02-10 18:05:45 +0100475 if isinstance(files[f], CardDF):
476 skip_df = False
477 try:
478 fcp_dec = self._cmd.rs.select(f, self._cmd)
479 except Exception as e:
480 skip_df = True
481 df = self._cmd.rs.selected_file
482 df_path_list = df.fully_qualified_path(True)
483 df_skip_reason_str = '/'.join(df_path_list) + \
484 "/" + str(f) + ", " + str(e)
485 if context:
486 context['DF_SKIP'] += 1
487 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200488
Harald Weltec91085e2022-02-10 18:05:45 +0100489 # If the DF was skipped, we never have entered the directory
490 # below, so we must not move up.
491 if skip_df == False:
Harald Welte08b11ab2022-02-10 18:56:41 +0100492 self.walk(indent + 1, action, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100493 fcp_dec = self._cmd.rs.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200494
Harald Weltec91085e2022-02-10 18:05:45 +0100495 elif action:
496 df_before_action = self._cmd.rs.selected_file
Harald Welte08b11ab2022-02-10 18:56:41 +0100497 action(f, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100498 # When walking through the file system tree the action must not
499 # always restore the currently selected file to the file that
500 # was selected before executing the action() callback.
501 if df_before_action != self._cmd.rs.selected_file:
502 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
503 % (str(self._cmd.rs.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100504
Harald Weltec91085e2022-02-10 18:05:45 +0100505 def do_tree(self, opts):
506 """Display a filesystem-tree with all selectable files"""
507 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100508
Harald Welte08b11ab2022-02-10 18:56:41 +0100509 def export(self, filename, context, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100510 """ Select and export a single file """
511 context['COUNT'] += 1
512 df = self._cmd.rs.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200513
Harald Weltec91085e2022-02-10 18:05:45 +0100514 if not isinstance(df, CardDF):
515 raise RuntimeError(
516 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200517
Harald Weltec91085e2022-02-10 18:05:45 +0100518 df_path_list = df.fully_qualified_path(True)
519 df_path_list_fid = df.fully_qualified_path(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100520
Harald Weltec91085e2022-02-10 18:05:45 +0100521 file_str = '/'.join(df_path_list) + "/" + str(filename)
522 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100523
Harald Weltec91085e2022-02-10 18:05:45 +0100524 self._cmd.poutput("# directory: %s (%s)" %
525 ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
526 try:
527 fcp_dec = self._cmd.rs.select(filename, self._cmd)
528 self._cmd.poutput("# file: %s (%s)" % (
529 self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100530
Harald Weltec91085e2022-02-10 18:05:45 +0100531 fd = fcp_dec['file_descriptor']
532 structure = fd['structure']
533 self._cmd.poutput("# structure: %s" % str(structure))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100534
Harald Weltec91085e2022-02-10 18:05:45 +0100535 for f in df_path_list:
536 self._cmd.poutput("select " + str(f))
537 self._cmd.poutput("select " + self._cmd.rs.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100538
Harald Weltec91085e2022-02-10 18:05:45 +0100539 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100540 if as_json:
541 result = self._cmd.rs.read_binary_dec()
542 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
543 else:
544 result = self._cmd.rs.read_binary()
545 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100546 elif structure == 'cyclic' or structure == 'linear_fixed':
547 # Use number of records specified in select response
548 if 'num_of_rec' in fd:
549 num_of_rec = fd['num_of_rec']
550 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100551 if as_json:
552 result = self._cmd.rs.read_record_dec(r)
553 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
554 else:
555 result = self._cmd.rs.read_record(r)
556 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
557
Harald Weltec91085e2022-02-10 18:05:45 +0100558 # When the select response does not return the number of records, read until we hit the
559 # first record that cannot be read.
560 else:
561 r = 1
562 while True:
563 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100564 if as_json:
565 result = self._cmd.rs.read_record_dec(r)
566 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
567 else:
568 result = self._cmd.rs.read_record(r)
569 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100570 except SwMatchError as e:
571 # We are past the last valid record - stop
572 if e.sw_actual == "9402":
573 break
574 # Some other problem occurred
575 else:
576 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100577 r = r + 1
578 elif structure == 'ber_tlv':
579 tags = self._cmd.rs.retrieve_tags()
580 for t in tags:
581 result = self._cmd.rs.retrieve_data(t)
582 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
583 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
584 else:
585 raise RuntimeError(
586 'Unsupported structure "%s" of file "%s"' % (structure, filename))
587 except Exception as e:
588 bad_file_str = '/'.join(df_path_list) + \
589 "/" + str(filename) + ", " + str(e)
590 self._cmd.poutput("# bad file: %s" % bad_file_str)
591 context['ERR'] += 1
592 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100593
Harald Weltec91085e2022-02-10 18:05:45 +0100594 # When reading the file is done, make sure the parent file is
595 # selected again. This will be the usual case, however we need
596 # to check before since we must not select the same DF twice
597 if df != self._cmd.rs.selected_file:
598 self._cmd.rs.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200599
Harald Weltec91085e2022-02-10 18:05:45 +0100600 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100601
Harald Weltec91085e2022-02-10 18:05:45 +0100602 export_parser = argparse.ArgumentParser()
603 export_parser.add_argument(
604 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100605 export_parser.add_argument(
606 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100607
Harald Weltec91085e2022-02-10 18:05:45 +0100608 @cmd2.with_argparser(export_parser)
609 def do_export(self, opts):
610 """Export files to script that can be imported back later"""
611 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
612 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
613 if opts.filename:
Harald Welte08b11ab2022-02-10 18:56:41 +0100614 self.export(opts.filename, context, opts.json)
Harald Weltec91085e2022-02-10 18:05:45 +0100615 else:
Harald Welte08b11ab2022-02-10 18:56:41 +0100616 self.walk(0, self.export, context, opts.json)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200617
Harald Weltec91085e2022-02-10 18:05:45 +0100618 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200619
Harald Weltec91085e2022-02-10 18:05:45 +0100620 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
621 self._cmd.poutput("# bad files: %u" % context['ERR'])
622 for b in context['BAD']:
623 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200624
Harald Weltec91085e2022-02-10 18:05:45 +0100625 self._cmd.poutput("# skipped dedicated files(s): %u" %
626 context['DF_SKIP'])
627 for b in context['DF_SKIP_REASON']:
628 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200629
Harald Weltec91085e2022-02-10 18:05:45 +0100630 if context['ERR'] and context['DF_SKIP']:
631 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
632 context['ERR'], context['DF_SKIP']))
633 elif context['ERR']:
634 raise RuntimeError(
635 "unable to export %i elementary file(s)" % context['ERR'])
636 elif context['DF_SKIP']:
637 raise RuntimeError(
638 "unable to export %i dedicated files(s)" % context['ERR'])
Harald Welteb2edd142021-01-08 23:29:35 +0100639
Harald Weltec91085e2022-02-10 18:05:45 +0100640 def do_reset(self, opts):
641 """Reset the Card."""
642 atr = self._cmd.rs.reset(self._cmd)
643 self._cmd.poutput('Card ATR: %s' % atr)
644 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200645
Harald Weltec91085e2022-02-10 18:05:45 +0100646 def do_desc(self, opts):
647 """Display human readable file description for the currently selected file"""
648 desc = self._cmd.rs.selected_file.desc
649 if desc:
650 self._cmd.poutput(desc)
651 else:
652 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200653
Harald Weltec91085e2022-02-10 18:05:45 +0100654 def do_verify_adm(self, arg):
655 """VERIFY the ADM1 PIN"""
656 if arg:
657 # use specified ADM-PIN
658 pin_adm = sanitize_pin_adm(arg)
659 else:
660 # try to find an ADM-PIN if none is specified
661 result = card_key_provider_get_field(
662 'ADM1', key='ICCID', value=self._cmd.iccid)
663 pin_adm = sanitize_pin_adm(result)
664 if pin_adm:
665 self._cmd.poutput(
666 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
667 else:
668 raise ValueError(
669 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200670
Harald Weltec91085e2022-02-10 18:05:45 +0100671 if pin_adm:
672 self._cmd.card.verify_adm(h2b(pin_adm))
673 else:
674 raise ValueError("error: cannot authenticate, no adm-pin!")
675
Harald Welteafb8d3f2022-02-11 16:03:06 +0100676 apdu_cmd_parser = argparse.ArgumentParser()
677 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
678
679 @cmd2.with_argparser(apdu_cmd_parser)
680 def do_apdu(self, opts):
681 """Send a raw APDU to the card, and print SW + Response.
682 DANGEROUS: pySim-shell will not know any card state changes, and
683 not continue to work as expected if you e.g. select a different file."""
684 data, sw = self._cmd.card._scc._tp.send_apdu(opts.APDU)
685 self._cmd.poutput("SW: %s %s, RESP: %s" % (sw, self._cmd.rs.interpret_sw(sw), data))
686
Harald Welteb2edd142021-01-08 23:29:35 +0100687
Harald Welte31d2cf02021-04-03 10:47:29 +0200688@with_default_category('ISO7816 Commands')
689class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100690 def __init__(self):
691 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200692
Harald Weltec91085e2022-02-10 18:05:45 +0100693 def do_select(self, opts):
694 """SELECT a File (ADF/DF/EF)"""
695 if len(opts.arg_list) == 0:
696 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
697 path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
698 False)
699 self._cmd.poutput("currently selected file: " +
700 '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
701 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200702
Harald Weltec91085e2022-02-10 18:05:45 +0100703 path = opts.arg_list[0]
704 fcp_dec = self._cmd.rs.select(path, self._cmd)
705 self._cmd.update_prompt()
706 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200707
Harald Weltec91085e2022-02-10 18:05:45 +0100708 def complete_select(self, text, line, begidx, endidx) -> List[str]:
709 """Command Line tab completion for SELECT"""
710 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
711 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200712
Harald Weltec91085e2022-02-10 18:05:45 +0100713 def get_code(self, code):
714 """Use code either directly or try to get it from external data source"""
715 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200716
Harald Weltec91085e2022-02-10 18:05:45 +0100717 if str(code).upper() not in auto:
718 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200719
Harald Weltec91085e2022-02-10 18:05:45 +0100720 result = card_key_provider_get_field(
721 str(code), key='ICCID', value=self._cmd.iccid)
722 result = sanitize_pin_adm(result)
723 if result:
724 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
725 (code.upper(), result, self._cmd.iccid))
726 else:
727 self._cmd.poutput("cannot find %s for ICCID '%s'" %
728 (code.upper(), self._cmd.iccid))
729 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200730
Harald Weltec91085e2022-02-10 18:05:45 +0100731 verify_chv_parser = argparse.ArgumentParser()
732 verify_chv_parser.add_argument(
733 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
734 verify_chv_parser.add_argument(
735 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200736
Harald Weltec91085e2022-02-10 18:05:45 +0100737 @cmd2.with_argparser(verify_chv_parser)
738 def do_verify_chv(self, opts):
739 """Verify (authenticate) using specified PIN code"""
740 pin = self.get_code(opts.pin_code)
741 (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin))
742 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200743
Harald Weltec91085e2022-02-10 18:05:45 +0100744 unblock_chv_parser = argparse.ArgumentParser()
745 unblock_chv_parser.add_argument(
746 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
747 unblock_chv_parser.add_argument(
748 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
749 unblock_chv_parser.add_argument(
750 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200751
Harald Weltec91085e2022-02-10 18:05:45 +0100752 @cmd2.with_argparser(unblock_chv_parser)
753 def do_unblock_chv(self, opts):
754 """Unblock PIN code using specified PUK code"""
755 new_pin = self.get_code(opts.new_pin_code)
756 puk = self.get_code(opts.puk_code)
757 (data, sw) = self._cmd.card._scc.unblock_chv(
758 opts.pin_nr, h2b(puk), h2b(new_pin))
759 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200760
Harald Weltec91085e2022-02-10 18:05:45 +0100761 change_chv_parser = argparse.ArgumentParser()
762 change_chv_parser.add_argument(
763 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
764 change_chv_parser.add_argument(
765 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
766 change_chv_parser.add_argument(
767 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200768
Harald Weltec91085e2022-02-10 18:05:45 +0100769 @cmd2.with_argparser(change_chv_parser)
770 def do_change_chv(self, opts):
771 """Change PIN code to a new PIN code"""
772 new_pin = self.get_code(opts.new_pin_code)
773 pin = self.get_code(opts.pin_code)
774 (data, sw) = self._cmd.card._scc.change_chv(
775 opts.pin_nr, h2b(pin), h2b(new_pin))
776 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200777
Harald Weltec91085e2022-02-10 18:05:45 +0100778 disable_chv_parser = argparse.ArgumentParser()
779 disable_chv_parser.add_argument(
780 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
781 disable_chv_parser.add_argument(
782 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 @cmd2.with_argparser(disable_chv_parser)
785 def do_disable_chv(self, opts):
786 """Disable PIN code using specified PIN code"""
787 pin = self.get_code(opts.pin_code)
788 (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin))
789 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200790
Harald Weltec91085e2022-02-10 18:05:45 +0100791 enable_chv_parser = argparse.ArgumentParser()
792 enable_chv_parser.add_argument(
793 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
794 enable_chv_parser.add_argument(
795 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
Harald Welte31d2cf02021-04-03 10:47:29 +0200796
Harald Weltec91085e2022-02-10 18:05:45 +0100797 @cmd2.with_argparser(enable_chv_parser)
798 def do_enable_chv(self, opts):
799 """Enable PIN code using specified PIN code"""
800 pin = self.get_code(opts.pin_code)
801 (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin))
802 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200803
Harald Weltec91085e2022-02-10 18:05:45 +0100804 def do_deactivate_file(self, opts):
805 """Deactivate the current EF"""
806 (data, sw) = self._cmd.card._scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200807
Harald Weltec91085e2022-02-10 18:05:45 +0100808 def do_activate_file(self, opts):
809 """Activate the specified EF"""
810 path = opts.arg_list[0]
811 (data, sw) = self._cmd.rs.activate_file(path)
Harald Welte485692b2021-05-25 22:21:44 +0200812
Harald Weltec91085e2022-02-10 18:05:45 +0100813 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
814 """Command Line tab completion for ACTIVATE FILE"""
815 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
816 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200817
Harald Weltec91085e2022-02-10 18:05:45 +0100818 open_chan_parser = argparse.ArgumentParser()
819 open_chan_parser.add_argument(
820 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200821
Harald Weltec91085e2022-02-10 18:05:45 +0100822 @cmd2.with_argparser(open_chan_parser)
823 def do_open_channel(self, opts):
824 """Open a logical channel."""
825 (data, sw) = self._cmd.card._scc.manage_channel(
826 mode='open', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200827
Harald Weltec91085e2022-02-10 18:05:45 +0100828 close_chan_parser = argparse.ArgumentParser()
829 close_chan_parser.add_argument(
830 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200831
Harald Weltec91085e2022-02-10 18:05:45 +0100832 @cmd2.with_argparser(close_chan_parser)
833 def do_close_channel(self, opts):
834 """Close a logical channel."""
835 (data, sw) = self._cmd.card._scc.manage_channel(
836 mode='close', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200837
Harald Weltec91085e2022-02-10 18:05:45 +0100838 def do_status(self, opts):
839 """Perform the STATUS command."""
840 fcp_dec = self._cmd.rs.status()
841 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200842
Harald Weltec91085e2022-02-10 18:05:45 +0100843 suspend_uicc_parser = argparse.ArgumentParser()
844 suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60,
845 help='Proposed minimum duration of suspension')
846 suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60,
847 help='Proposed maximum duration of suspension')
Harald Welteec950532021-10-20 13:09:00 +0200848
Harald Weltec91085e2022-02-10 18:05:45 +0100849 # not ISO7816-4 but TS 102 221
850 @cmd2.with_argparser(suspend_uicc_parser)
851 def do_suspend_uicc(self, opts):
852 """Perform the SUSPEND UICC command. Only supported on some UICC."""
853 (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
854 max_len_secs=opts.max_duration_secs)
855 self._cmd.poutput(
856 'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
Harald Welteec950532021-10-20 13:09:00 +0200857
Harald Welte703f9332021-04-10 18:39:32 +0200858
Harald Weltef2e761c2021-04-11 11:56:44 +0200859option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
860 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200861argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200862
863global_group = option_parser.add_argument_group('General Options')
864global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200865 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100866global_group.add_argument('--csv', metavar='FILE',
867 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +0200868global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +0100869 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +0200870
871adm_group = global_group.add_mutually_exclusive_group()
872adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
873 help='ADM PIN used for provisioning (overwrites default)')
874adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
875 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +0100876
877
878if __name__ == '__main__':
879
Harald Weltec91085e2022-02-10 18:05:45 +0100880 # Parse options
881 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +0100882
Harald Weltec91085e2022-02-10 18:05:45 +0100883 # If a script file is specified, be sure that it actually exists
884 if opts.script:
885 if not os.access(opts.script, os.R_OK):
886 print("Invalid script file!")
887 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +0200888
Harald Weltec91085e2022-02-10 18:05:45 +0100889 # Register csv-file as card data provider, either from specified CSV
890 # or from CSV file in home directory
891 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
892 if opts.csv:
893 card_key_provider_register(CardKeyProviderCsv(opts.csv))
894 if os.path.isfile(csv_default):
895 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +0100896
Harald Weltec91085e2022-02-10 18:05:45 +0100897 # Init card reader driver
898 sl = init_reader(opts)
899 if sl is None:
900 exit(1)
Philipp Maierea95c392021-09-16 13:10:19 +0200901
Harald Weltec91085e2022-02-10 18:05:45 +0100902 # Create command layer
903 scc = SimCardCommands(transport=sl)
Philipp Maierea95c392021-09-16 13:10:19 +0200904
Harald Weltec91085e2022-02-10 18:05:45 +0100905 # Create a card handler (for bulk provisioning)
906 if opts.card_handler_config:
907 ch = CardHandlerAuto(None, opts.card_handler_config)
908 else:
909 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +0200910
Harald Weltec91085e2022-02-10 18:05:45 +0100911 # Detect and initialize the card in the reader. This may fail when there
912 # is no card in the reader or the card is unresponsive. PysimApp is
913 # able to tolerate and recover from that.
914 try:
915 rs, card = init_card(sl)
916 app = PysimApp(card, rs, sl, ch, opts.script)
917 except:
918 print("Card initialization failed with an exception:")
919 print("---------------------8<---------------------")
920 traceback.print_exc()
921 print("---------------------8<---------------------")
922 print("(you may still try to recover from this manually by using the 'equip' command.)")
923 print(
924 " it should also be noted that some readers may behave strangely when no card")
925 print(" is inserted.)")
926 print("")
927 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +0200928
Harald Weltec91085e2022-02-10 18:05:45 +0100929 # If the user supplies an ADM PIN at via commandline args authenticate
930 # immediately so that the user does not have to use the shell commands
931 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
932 if pin_adm:
933 if not card:
934 print("Card error, cannot do ADM verification with supplied ADM pin now.")
935 try:
936 card.verify_adm(h2b(pin_adm))
937 except Exception as e:
938 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +0100939
Harald Weltec91085e2022-02-10 18:05:45 +0100940 app.cmdloop()