blob: 06977f049331fc19d73637d661603d934a4f9721 [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 Welte3c9b7842021-10-19 21:44:24 +020052from pySim.ts_102_222 import Ts102222Commands
Harald Welte5ce35242021-04-02 20:27:05 +020053from pySim.ts_31_102 import CardApplicationUSIM
54from pySim.ts_31_103 import CardApplicationISIM
Harald Welte95ce6b12021-10-20 18:40:54 +020055from pySim.ara_m import CardApplicationARAM
Harald Welte34eb5042022-02-21 17:19:28 +010056from pySim.global_platform import CardApplicationISD
Harald Welte2a33ad22021-10-20 10:14:18 +020057from pySim.gsm_r import DF_EIRENE
Harald Welteb2edd142021-01-08 23:29:35 +010058
Harald Welte4c1dca02021-10-14 17:48:25 +020059# we need to import this module so that the SysmocomSJA2 sub-class of
60# CardModel is created, which will add the ATR-based matching and
61# calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models
Harald Weltef44256c2021-10-14 15:53:39 +020062import pySim.sysmocom_sja2
63
Harald Welte4442b3d2021-04-03 09:00:16 +020064from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010065
Harald Weltec91085e2022-02-10 18:05:45 +010066
Philipp Maierea95c392021-09-16 13:10:19 +020067def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010068 """
69 Detect card in reader and setup card profile and runtime state. This
70 function must be called at least once on startup. The card and runtime
71 state object (rs) is required for all pySim-shell commands.
72 """
Philipp Maierea95c392021-09-16 13:10:19 +020073
Harald Weltec91085e2022-02-10 18:05:45 +010074 # Wait up to three seconds for a card in reader and try to detect
75 # the card type.
76 print("Waiting for card...")
77 try:
78 sl.wait_for_card(3)
79 except NoCardError:
80 print("No card detected!")
81 return None, None
82 except:
83 print("Card not readable!")
84 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020085
Harald Weltec91085e2022-02-10 18:05:45 +010086 card = card_detect("auto", scc)
87 if card is None:
88 print("Warning: Could not detect card type - assuming a generic card type...")
89 card = SimCard(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +010090
Harald Weltec91085e2022-02-10 18:05:45 +010091 profile = CardProfile.pick(scc)
92 if profile is None:
93 print("Unsupported card type!")
94 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020095
Harald Weltec91085e2022-02-10 18:05:45 +010096 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +010097
Harald Weltec91085e2022-02-10 18:05:45 +010098 # FIXME: This shouln't be here, the profile should add the applications,
99 # however, we cannot simply put his into ts_102_221.py since we would
100 # have to e.g. import CardApplicationUSIM from ts_31_102.py, which already
101 # imports from ts_102_221.py. This means we will end up with a circular
102 # import, which needs to be resolved first.
103 if isinstance(profile, CardProfileUICC):
104 profile.add_application(CardApplicationUSIM())
105 profile.add_application(CardApplicationISIM())
106 profile.add_application(CardApplicationARAM())
Harald Welte34eb5042022-02-21 17:19:28 +0100107 profile.add_application(CardApplicationISD())
Philipp Maiera028c7d2021-11-08 16:12:03 +0100108
Harald Weltec91085e2022-02-10 18:05:45 +0100109 # Create runtime state with card profile
110 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200111
Harald Weltec91085e2022-02-10 18:05:45 +0100112 # FIXME: This is an GSM-R related file, it needs to be added throughout,
113 # the profile. At the moment we add it for all cards, this won't hurt,
114 # but regular SIM and UICC will not have it and fail to select it.
115 rs.mf.add_file(DF_EIRENE())
Philipp Maierea95c392021-09-16 13:10:19 +0200116
Harald Weltec91085e2022-02-10 18:05:45 +0100117 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200118
Harald Weltec91085e2022-02-10 18:05:45 +0100119 # inform the transport that we can do context-specific SW interpretation
120 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200121
Harald Weltec91085e2022-02-10 18:05:45 +0100122 return rs, card
123
Philipp Maier2b11c322021-03-17 12:37:39 +0100124
Harald Welteb2edd142021-01-08 23:29:35 +0100125class PysimApp(cmd2.Cmd):
Harald Weltec91085e2022-02-10 18:05:45 +0100126 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier76667642021-09-22 16:53:22 +0200127
Harald Weltec91085e2022-02-10 18:05:45 +0100128 def __init__(self, card, rs, sl, ch, script=None):
129 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
130 use_ipython=True, auto_load_commands=False, startup_script=script)
131 self.intro = style('Welcome to pySim-shell!', fg=fg.red)
132 self.default_category = 'pySim-shell built-in commands'
133 self.card = None
134 self.rs = None
135 self.py_locals = {'card': self.card, 'rs': self.rs}
136 self.sl = sl
137 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200138
Harald Weltec91085e2022-02-10 18:05:45 +0100139 self.numeric_path = False
140 self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
141 onchange_cb=self._onchange_numeric_path))
142 self.conserve_write = True
143 self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
144 onchange_cb=self._onchange_conserve_write))
145 self.json_pretty_print = True
146 self.add_settable(cmd2.Settable('json_pretty_print',
147 bool, 'Pretty-Print JSON output'))
148 self.apdu_trace = False
149 self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
150 onchange_cb=self._onchange_apdu_trace))
Philipp Maier5d698e52021-09-16 13:18:01 +0200151
Harald Weltec91085e2022-02-10 18:05:45 +0100152 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200153
Harald Weltec91085e2022-02-10 18:05:45 +0100154 def equip(self, card, rs):
155 """
156 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
157 and commands to enable card operations.
158 """
Philipp Maier76667642021-09-22 16:53:22 +0200159
Harald Weltec91085e2022-02-10 18:05:45 +0100160 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200161
Harald Weltec91085e2022-02-10 18:05:45 +0100162 # Unequip everything from pySim-shell that would not work in unequipped state
163 if self.rs:
164 self.rs.unregister_cmds(self)
165 for cmds in [Iso7816Commands, PySimCommands]:
166 cmd_set = self.find_commandsets(cmds)
167 if cmd_set:
168 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200169
Harald Weltec91085e2022-02-10 18:05:45 +0100170 self.card = card
171 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200172
Harald Weltec91085e2022-02-10 18:05:45 +0100173 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
174 # needed to operate on cards.
175 if self.card and self.rs:
176 self._onchange_conserve_write(
177 'conserve_write', False, self.conserve_write)
178 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
179 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200180 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100181 self.register_command_set(PySimCommands())
182 self.iccid, sw = self.card.read_iccid()
183 rs.select('MF', self)
184 rc = True
185 else:
186 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200187
Harald Weltec91085e2022-02-10 18:05:45 +0100188 self.update_prompt()
189 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100190
Harald Weltec91085e2022-02-10 18:05:45 +0100191 def poutput_json(self, data, force_no_pretty=False):
192 """like cmd2.poutput() but for a JSON serializable dict."""
193 if force_no_pretty or self.json_pretty_print == False:
194 output = json.dumps(data, cls=JsonEncoder)
195 else:
196 output = json.dumps(data, cls=JsonEncoder, indent=4)
197 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100198
Harald Weltec91085e2022-02-10 18:05:45 +0100199 def _onchange_numeric_path(self, param_name, old, new):
200 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100201
Harald Weltec91085e2022-02-10 18:05:45 +0100202 def _onchange_conserve_write(self, param_name, old, new):
203 if self.rs:
204 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200205
Harald Weltec91085e2022-02-10 18:05:45 +0100206 def _onchange_apdu_trace(self, param_name, old, new):
207 if self.card:
208 if new == True:
209 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
210 else:
211 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200212
Harald Weltec91085e2022-02-10 18:05:45 +0100213 class Cmd2ApduTracer(ApduTracer):
214 def __init__(self, cmd2_app):
215 self.cmd2 = app
Harald Welte7829d8a2021-04-10 11:28:53 +0200216
Harald Weltec91085e2022-02-10 18:05:45 +0100217 def trace_response(self, cmd, sw, resp):
218 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
219 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100220
Harald Weltec91085e2022-02-10 18:05:45 +0100221 def update_prompt(self):
222 if self.rs:
223 path_list = self.rs.selected_file.fully_qualified_path(
224 not self.numeric_path)
225 self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
226 else:
227 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100228
Harald Weltec91085e2022-02-10 18:05:45 +0100229 @cmd2.with_category(CUSTOM_CATEGORY)
230 def do_intro(self, _):
231 """Display the intro banner"""
232 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100233
Harald Weltec91085e2022-02-10 18:05:45 +0100234 def do_eof(self, _: argparse.Namespace) -> bool:
235 self.poutput("")
236 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200237
Harald Weltec91085e2022-02-10 18:05:45 +0100238 @cmd2.with_category(CUSTOM_CATEGORY)
239 def do_equip(self, opts):
240 """Equip pySim-shell with card"""
241 rs, card = init_card(sl)
242 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200243
Harald Weltec91085e2022-02-10 18:05:45 +0100244 class InterceptStderr(list):
245 def __init__(self):
246 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200247
Harald Weltec91085e2022-02-10 18:05:45 +0100248 def __enter__(self):
249 self._stringio_stderr = StringIO()
250 sys.stderr = self._stringio_stderr
251 return self
Philipp Maier76667642021-09-22 16:53:22 +0200252
Harald Weltec91085e2022-02-10 18:05:45 +0100253 def __exit__(self, *args):
254 self.stderr = self._stringio_stderr.getvalue().strip()
255 del self._stringio_stderr
256 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200257
Harald Weltec91085e2022-02-10 18:05:45 +0100258 def _show_failure_sign(self):
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(style(" + ### +", fg=fg.bright_red))
263 self.poutput(style(" + ## ## +", fg=fg.bright_red))
264 self.poutput(style(" + ## ## +", fg=fg.bright_red))
265 self.poutput(style(" +-------------+", fg=fg.bright_red))
266 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200267
Harald Weltec91085e2022-02-10 18:05:45 +0100268 def _show_success_sign(self):
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(style(" + # ## +", fg=fg.bright_green))
273 self.poutput(style(" + ## # +", fg=fg.bright_green))
274 self.poutput(style(" + ## +", fg=fg.bright_green))
275 self.poutput(style(" +-------------+", fg=fg.bright_green))
276 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200277
Harald Weltec91085e2022-02-10 18:05:45 +0100278 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200279
Harald Weltec91085e2022-02-10 18:05:45 +0100280 # Early phase of card initialzation (this part may fail with an exception)
281 try:
282 rs, card = init_card(self.sl)
283 rc = self.equip(card, rs)
284 except:
285 self.poutput("")
286 self.poutput("Card initialization failed with an exception:")
287 self.poutput("---------------------8<---------------------")
288 traceback.print_exc()
289 self.poutput("---------------------8<---------------------")
290 self.poutput("")
291 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200292
Harald Weltec91085e2022-02-10 18:05:45 +0100293 # Actual card processing step. This part should never fail with an exception since the cmd2
294 # do_run_script method will catch any exception that might occur during script execution.
295 if rc:
296 self.poutput("")
297 self.poutput("Transcript stdout:")
298 self.poutput("---------------------8<---------------------")
299 with self.InterceptStderr() as logged:
300 self.do_run_script(script_path)
301 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200302
Harald Weltec91085e2022-02-10 18:05:45 +0100303 self.poutput("")
304 self.poutput("Transcript stderr:")
305 if logged.stderr:
306 self.poutput("---------------------8<---------------------")
307 self.poutput(logged.stderr)
308 self.poutput("---------------------8<---------------------")
309 else:
310 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200311
Harald Weltec91085e2022-02-10 18:05:45 +0100312 # Check for exceptions
313 self.poutput("")
314 if "EXCEPTION of type" not in logged.stderr:
315 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200316
Harald Weltec91085e2022-02-10 18:05:45 +0100317 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200318
Harald Weltec91085e2022-02-10 18:05:45 +0100319 bulk_script_parser = argparse.ArgumentParser()
320 bulk_script_parser.add_argument(
321 'script_path', help="path to the script file")
322 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
323 action='store_true')
324 bulk_script_parser.add_argument('--tries', type=int, default=2,
325 help='how many tries before trying the next card')
326 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
327 help='commandline to execute when card handling has stopped')
328 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
329 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200330
Harald Weltec91085e2022-02-10 18:05:45 +0100331 @cmd2.with_argparser(bulk_script_parser)
332 @cmd2.with_category(CUSTOM_CATEGORY)
333 def do_bulk_script(self, opts):
334 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200335
Harald Weltec91085e2022-02-10 18:05:45 +0100336 # Make sure that the script file exists and that it is readable.
337 if not os.access(opts.script_path, os.R_OK):
338 self.poutput("Invalid script file!")
339 return
Philipp Maier76667642021-09-22 16:53:22 +0200340
Harald Weltec91085e2022-02-10 18:05:45 +0100341 success_count = 0
342 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200343
Harald Weltec91085e2022-02-10 18:05:45 +0100344 first = True
345 while 1:
346 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
347 # The ratinale is: There may be a problem with the device, we do want to prevent that
348 # all remaining cards are fired to the error bin. This is only relevant for situations
349 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200350
Harald Weltec91085e2022-02-10 18:05:45 +0100351 try:
352 # In case of failure, try multiple times.
353 for i in range(opts.tries):
354 # fetch card into reader bay
355 ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200356
Harald Weltec91085e2022-02-10 18:05:45 +0100357 # if necessary execute an action before we start processing the card
358 if(opts.pre_card_action):
359 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200360
Harald Weltec91085e2022-02-10 18:05:45 +0100361 # process the card
362 rc = self._process_card(first, opts.script_path)
363 if rc == 0:
364 success_count = success_count + 1
365 self._show_success_sign()
366 self.poutput("Statistics: success :%i, failure: %i" % (
367 success_count, fail_count))
368 break
369 else:
370 fail_count = fail_count + 1
371 self._show_failure_sign()
372 self.poutput("Statistics: success :%i, failure: %i" % (
373 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200374
Harald Weltec91085e2022-02-10 18:05:45 +0100375 # Depending on success or failure, the card goes either in the "error" bin or in the
376 # "done" bin.
377 if rc < 0:
378 ch.error()
379 else:
380 ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200381
Harald Weltec91085e2022-02-10 18:05:45 +0100382 # In most cases it is possible to proceed with the next card, but the
383 # user may decide to halt immediately when an error occurs
384 if opts.halt_on_error and rc < 0:
385 return
Philipp Maier76667642021-09-22 16:53:22 +0200386
Harald Weltec91085e2022-02-10 18:05:45 +0100387 except (KeyboardInterrupt):
388 self.poutput("")
389 self.poutput("Terminated by user!")
390 return
391 except (SystemExit):
392 # When all cards are processed the card handler device will throw a SystemExit
393 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
394 # The user has the option to execute some action to make aware that the card handler
395 # needs service.
396 if(opts.on_stop_action):
397 os.system(opts.on_stop_action)
398 return
399 except:
400 self.poutput("")
401 self.poutput("Card handling failed with an exception:")
402 self.poutput("---------------------8<---------------------")
403 traceback.print_exc()
404 self.poutput("---------------------8<---------------------")
405 self.poutput("")
406 fail_count = fail_count + 1
407 self._show_failure_sign()
408 self.poutput("Statistics: success :%i, failure: %i" %
409 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200410
Harald Weltec91085e2022-02-10 18:05:45 +0100411 first = False
412
413 echo_parser = argparse.ArgumentParser()
414 echo_parser.add_argument('string', help="string to echo on the shell")
415
416 @cmd2.with_argparser(echo_parser)
417 @cmd2.with_category(CUSTOM_CATEGORY)
418 def do_echo(self, opts):
419 """Echo (print) a string on the console"""
420 self.poutput(opts.string)
421
Harald Welteb2edd142021-01-08 23:29:35 +0100422
Harald Welte31d2cf02021-04-03 10:47:29 +0200423@with_default_category('pySim Commands')
424class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100425 def __init__(self):
426 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100427
Harald Weltec91085e2022-02-10 18:05:45 +0100428 dir_parser = argparse.ArgumentParser()
429 dir_parser.add_argument(
430 '--fids', help='Show file identifiers', action='store_true')
431 dir_parser.add_argument(
432 '--names', help='Show file names', action='store_true')
433 dir_parser.add_argument(
434 '--apps', help='Show applications', action='store_true')
435 dir_parser.add_argument(
436 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100437
Harald Weltec91085e2022-02-10 18:05:45 +0100438 @cmd2.with_argparser(dir_parser)
439 def do_dir(self, opts):
440 """Show a listing of files available in currently selected DF or MF"""
441 if opts.all:
442 flags = []
443 elif opts.fids or opts.names or opts.apps:
444 flags = ['PARENT', 'SELF']
445 if opts.fids:
446 flags += ['FIDS', 'AIDS']
447 if opts.names:
448 flags += ['FNAMES', 'ANAMES']
449 if opts.apps:
450 flags += ['ANAMES', 'AIDS']
451 else:
452 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
453 selectables = list(
454 self._cmd.rs.selected_file.get_selectable_names(flags=flags))
455 directory_str = tabulate_str_list(
456 selectables, width=79, hspace=2, lspace=1, align_left=True)
457 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
458 self._cmd.poutput('/'.join(path_list))
459 path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
460 self._cmd.poutput('/'.join(path_list))
461 self._cmd.poutput(directory_str)
462 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100463
Harald Welte08b11ab2022-02-10 18:56:41 +0100464 def walk(self, indent=0, action=None, context=None, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100465 """Recursively walk through the file system, starting at the currently selected DF"""
466 files = self._cmd.rs.selected_file.get_selectables(
467 flags=['FNAMES', 'ANAMES'])
468 for f in files:
469 if not action:
470 output_str = " " * indent + str(f) + (" " * 250)
471 output_str = output_str[0:25]
472 if isinstance(files[f], CardADF):
473 output_str += " " + str(files[f].aid)
474 else:
475 output_str += " " + str(files[f].fid)
476 output_str += " " + str(files[f].desc)
477 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200478
Harald Weltec91085e2022-02-10 18:05:45 +0100479 if isinstance(files[f], CardDF):
480 skip_df = False
481 try:
482 fcp_dec = self._cmd.rs.select(f, self._cmd)
483 except Exception as e:
484 skip_df = True
485 df = self._cmd.rs.selected_file
486 df_path_list = df.fully_qualified_path(True)
487 df_skip_reason_str = '/'.join(df_path_list) + \
488 "/" + str(f) + ", " + str(e)
489 if context:
490 context['DF_SKIP'] += 1
491 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200492
Harald Weltec91085e2022-02-10 18:05:45 +0100493 # If the DF was skipped, we never have entered the directory
494 # below, so we must not move up.
495 if skip_df == False:
Harald Welte08b11ab2022-02-10 18:56:41 +0100496 self.walk(indent + 1, action, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100497 fcp_dec = self._cmd.rs.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200498
Harald Weltec91085e2022-02-10 18:05:45 +0100499 elif action:
500 df_before_action = self._cmd.rs.selected_file
Harald Welte08b11ab2022-02-10 18:56:41 +0100501 action(f, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100502 # When walking through the file system tree the action must not
503 # always restore the currently selected file to the file that
504 # was selected before executing the action() callback.
505 if df_before_action != self._cmd.rs.selected_file:
506 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
507 % (str(self._cmd.rs.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100508
Harald Weltec91085e2022-02-10 18:05:45 +0100509 def do_tree(self, opts):
510 """Display a filesystem-tree with all selectable files"""
511 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100512
Harald Welte08b11ab2022-02-10 18:56:41 +0100513 def export(self, filename, context, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100514 """ Select and export a single file """
515 context['COUNT'] += 1
516 df = self._cmd.rs.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200517
Harald Weltec91085e2022-02-10 18:05:45 +0100518 if not isinstance(df, CardDF):
519 raise RuntimeError(
520 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200521
Harald Weltec91085e2022-02-10 18:05:45 +0100522 df_path_list = df.fully_qualified_path(True)
523 df_path_list_fid = df.fully_qualified_path(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100524
Harald Weltec91085e2022-02-10 18:05:45 +0100525 file_str = '/'.join(df_path_list) + "/" + str(filename)
526 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100527
Harald Weltec91085e2022-02-10 18:05:45 +0100528 self._cmd.poutput("# directory: %s (%s)" %
529 ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
530 try:
531 fcp_dec = self._cmd.rs.select(filename, self._cmd)
532 self._cmd.poutput("# file: %s (%s)" % (
533 self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100534
Harald Welte747a9782022-02-13 17:52:28 +0100535 structure = self._cmd.rs.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100536 self._cmd.poutput("# structure: %s" % str(structure))
Harald Welte2bb17f32022-02-15 15:41:55 +0100537 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.rs.selected_file_fcp_hex))
538 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.rs.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100539
Harald Weltec91085e2022-02-10 18:05:45 +0100540 for f in df_path_list:
541 self._cmd.poutput("select " + str(f))
542 self._cmd.poutput("select " + self._cmd.rs.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100543
Harald Weltec91085e2022-02-10 18:05:45 +0100544 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100545 if as_json:
546 result = self._cmd.rs.read_binary_dec()
547 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
548 else:
549 result = self._cmd.rs.read_binary()
550 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100551 elif structure == 'cyclic' or structure == 'linear_fixed':
552 # Use number of records specified in select response
Harald Welte747a9782022-02-13 17:52:28 +0100553 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
554 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100555 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100556 if as_json:
557 result = self._cmd.rs.read_record_dec(r)
558 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
559 else:
560 result = self._cmd.rs.read_record(r)
561 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
562
Harald Weltec91085e2022-02-10 18:05:45 +0100563 # When the select response does not return the number of records, read until we hit the
564 # first record that cannot be read.
565 else:
566 r = 1
567 while True:
568 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100569 if as_json:
570 result = self._cmd.rs.read_record_dec(r)
571 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
572 else:
573 result = self._cmd.rs.read_record(r)
574 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100575 except SwMatchError as e:
576 # We are past the last valid record - stop
577 if e.sw_actual == "9402":
578 break
579 # Some other problem occurred
580 else:
581 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100582 r = r + 1
583 elif structure == 'ber_tlv':
584 tags = self._cmd.rs.retrieve_tags()
585 for t in tags:
586 result = self._cmd.rs.retrieve_data(t)
587 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
588 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
589 else:
590 raise RuntimeError(
591 'Unsupported structure "%s" of file "%s"' % (structure, filename))
592 except Exception as e:
593 bad_file_str = '/'.join(df_path_list) + \
594 "/" + str(filename) + ", " + str(e)
595 self._cmd.poutput("# bad file: %s" % bad_file_str)
596 context['ERR'] += 1
597 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100598
Harald Weltec91085e2022-02-10 18:05:45 +0100599 # When reading the file is done, make sure the parent file is
600 # selected again. This will be the usual case, however we need
601 # to check before since we must not select the same DF twice
602 if df != self._cmd.rs.selected_file:
603 self._cmd.rs.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200604
Harald Weltec91085e2022-02-10 18:05:45 +0100605 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100606
Harald Weltec91085e2022-02-10 18:05:45 +0100607 export_parser = argparse.ArgumentParser()
608 export_parser.add_argument(
609 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100610 export_parser.add_argument(
611 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100612
Harald Weltec91085e2022-02-10 18:05:45 +0100613 @cmd2.with_argparser(export_parser)
614 def do_export(self, opts):
615 """Export files to script that can be imported back later"""
616 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
617 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
618 if opts.filename:
Harald Welte08b11ab2022-02-10 18:56:41 +0100619 self.export(opts.filename, context, opts.json)
Harald Weltec91085e2022-02-10 18:05:45 +0100620 else:
Harald Welte08b11ab2022-02-10 18:56:41 +0100621 self.walk(0, self.export, context, opts.json)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200622
Harald Weltec91085e2022-02-10 18:05:45 +0100623 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200624
Harald Weltec91085e2022-02-10 18:05:45 +0100625 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
626 self._cmd.poutput("# bad files: %u" % context['ERR'])
627 for b in context['BAD']:
628 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200629
Harald Weltec91085e2022-02-10 18:05:45 +0100630 self._cmd.poutput("# skipped dedicated files(s): %u" %
631 context['DF_SKIP'])
632 for b in context['DF_SKIP_REASON']:
633 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200634
Harald Weltec91085e2022-02-10 18:05:45 +0100635 if context['ERR'] and context['DF_SKIP']:
636 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
637 context['ERR'], context['DF_SKIP']))
638 elif context['ERR']:
639 raise RuntimeError(
640 "unable to export %i elementary file(s)" % context['ERR'])
641 elif context['DF_SKIP']:
642 raise RuntimeError(
643 "unable to export %i dedicated files(s)" % context['ERR'])
Harald Welteb2edd142021-01-08 23:29:35 +0100644
Harald Weltec91085e2022-02-10 18:05:45 +0100645 def do_reset(self, opts):
646 """Reset the Card."""
647 atr = self._cmd.rs.reset(self._cmd)
648 self._cmd.poutput('Card ATR: %s' % atr)
649 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200650
Harald Weltec91085e2022-02-10 18:05:45 +0100651 def do_desc(self, opts):
652 """Display human readable file description for the currently selected file"""
653 desc = self._cmd.rs.selected_file.desc
654 if desc:
655 self._cmd.poutput(desc)
656 else:
657 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200658
Harald Weltec91085e2022-02-10 18:05:45 +0100659 def do_verify_adm(self, arg):
660 """VERIFY the ADM1 PIN"""
661 if arg:
662 # use specified ADM-PIN
663 pin_adm = sanitize_pin_adm(arg)
664 else:
665 # try to find an ADM-PIN if none is specified
666 result = card_key_provider_get_field(
667 'ADM1', key='ICCID', value=self._cmd.iccid)
668 pin_adm = sanitize_pin_adm(result)
669 if pin_adm:
670 self._cmd.poutput(
671 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
672 else:
673 raise ValueError(
674 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200675
Harald Weltec91085e2022-02-10 18:05:45 +0100676 if pin_adm:
677 self._cmd.card.verify_adm(h2b(pin_adm))
678 else:
679 raise ValueError("error: cannot authenticate, no adm-pin!")
680
Harald Welteafb8d3f2022-02-11 16:03:06 +0100681 apdu_cmd_parser = argparse.ArgumentParser()
682 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
683
684 @cmd2.with_argparser(apdu_cmd_parser)
685 def do_apdu(self, opts):
686 """Send a raw APDU to the card, and print SW + Response.
687 DANGEROUS: pySim-shell will not know any card state changes, and
Harald Welte12af7932022-02-15 16:39:08 +0100688 not continue to work as expected if you e.g. select a different
689 file."""
Harald Welteafb8d3f2022-02-11 16:03:06 +0100690 data, sw = self._cmd.card._scc._tp.send_apdu(opts.APDU)
691 self._cmd.poutput("SW: %s %s, RESP: %s" % (sw, self._cmd.rs.interpret_sw(sw), data))
692
Harald Welteb2edd142021-01-08 23:29:35 +0100693
Harald Welte31d2cf02021-04-03 10:47:29 +0200694@with_default_category('ISO7816 Commands')
695class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100696 def __init__(self):
697 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200698
Harald Weltec91085e2022-02-10 18:05:45 +0100699 def do_select(self, opts):
700 """SELECT a File (ADF/DF/EF)"""
701 if len(opts.arg_list) == 0:
702 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
703 path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
704 False)
705 self._cmd.poutput("currently selected file: " +
706 '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
707 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200708
Harald Weltec91085e2022-02-10 18:05:45 +0100709 path = opts.arg_list[0]
710 fcp_dec = self._cmd.rs.select(path, self._cmd)
711 self._cmd.update_prompt()
712 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200713
Harald Weltec91085e2022-02-10 18:05:45 +0100714 def complete_select(self, text, line, begidx, endidx) -> List[str]:
715 """Command Line tab completion for SELECT"""
716 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
717 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200718
Harald Weltec91085e2022-02-10 18:05:45 +0100719 def get_code(self, code):
720 """Use code either directly or try to get it from external data source"""
721 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200722
Harald Weltec91085e2022-02-10 18:05:45 +0100723 if str(code).upper() not in auto:
724 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200725
Harald Weltec91085e2022-02-10 18:05:45 +0100726 result = card_key_provider_get_field(
727 str(code), key='ICCID', value=self._cmd.iccid)
728 result = sanitize_pin_adm(result)
729 if result:
730 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
731 (code.upper(), result, self._cmd.iccid))
732 else:
733 self._cmd.poutput("cannot find %s for ICCID '%s'" %
734 (code.upper(), self._cmd.iccid))
735 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200736
Harald Weltec91085e2022-02-10 18:05:45 +0100737 verify_chv_parser = argparse.ArgumentParser()
738 verify_chv_parser.add_argument(
739 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
740 verify_chv_parser.add_argument(
741 '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 +0200742
Harald Weltec91085e2022-02-10 18:05:45 +0100743 @cmd2.with_argparser(verify_chv_parser)
744 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100745 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
746 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
747 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100748 pin = self.get_code(opts.pin_code)
749 (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin))
750 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200751
Harald Weltec91085e2022-02-10 18:05:45 +0100752 unblock_chv_parser = argparse.ArgumentParser()
753 unblock_chv_parser.add_argument(
754 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
755 unblock_chv_parser.add_argument(
756 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
757 unblock_chv_parser.add_argument(
758 '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 +0200759
Harald Weltec91085e2022-02-10 18:05:45 +0100760 @cmd2.with_argparser(unblock_chv_parser)
761 def do_unblock_chv(self, opts):
762 """Unblock PIN code using specified PUK code"""
763 new_pin = self.get_code(opts.new_pin_code)
764 puk = self.get_code(opts.puk_code)
765 (data, sw) = self._cmd.card._scc.unblock_chv(
766 opts.pin_nr, h2b(puk), h2b(new_pin))
767 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200768
Harald Weltec91085e2022-02-10 18:05:45 +0100769 change_chv_parser = argparse.ArgumentParser()
770 change_chv_parser.add_argument(
771 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
772 change_chv_parser.add_argument(
773 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
774 change_chv_parser.add_argument(
775 '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 +0200776
Harald Weltec91085e2022-02-10 18:05:45 +0100777 @cmd2.with_argparser(change_chv_parser)
778 def do_change_chv(self, opts):
779 """Change PIN code to a new PIN code"""
780 new_pin = self.get_code(opts.new_pin_code)
781 pin = self.get_code(opts.pin_code)
782 (data, sw) = self._cmd.card._scc.change_chv(
783 opts.pin_nr, h2b(pin), h2b(new_pin))
784 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200785
Harald Weltec91085e2022-02-10 18:05:45 +0100786 disable_chv_parser = argparse.ArgumentParser()
787 disable_chv_parser.add_argument(
788 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
789 disable_chv_parser.add_argument(
790 '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 +0200791
Harald Weltec91085e2022-02-10 18:05:45 +0100792 @cmd2.with_argparser(disable_chv_parser)
793 def do_disable_chv(self, opts):
794 """Disable PIN code using specified PIN code"""
795 pin = self.get_code(opts.pin_code)
796 (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin))
797 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200798
Harald Weltec91085e2022-02-10 18:05:45 +0100799 enable_chv_parser = argparse.ArgumentParser()
800 enable_chv_parser.add_argument(
801 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
802 enable_chv_parser.add_argument(
803 '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 +0200804
Harald Weltec91085e2022-02-10 18:05:45 +0100805 @cmd2.with_argparser(enable_chv_parser)
806 def do_enable_chv(self, opts):
807 """Enable PIN code using specified PIN code"""
808 pin = self.get_code(opts.pin_code)
809 (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin))
810 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200811
Harald Weltec91085e2022-02-10 18:05:45 +0100812 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100813 """Deactivate the currently selected EF"""
Harald Weltec91085e2022-02-10 18:05:45 +0100814 (data, sw) = self._cmd.card._scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200815
Harald Welte799c3542022-02-15 15:56:28 +0100816 activate_file_parser = argparse.ArgumentParser()
817 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
818 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100819 def do_activate_file(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100820 """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
821 SIM. You need to specify the name or FID of the file to activate."""
Harald Welte799c3542022-02-15 15:56:28 +0100822 (data, sw) = self._cmd.rs.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200823
Harald Weltec91085e2022-02-10 18:05:45 +0100824 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
825 """Command Line tab completion for ACTIVATE FILE"""
826 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
827 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200828
Harald Weltec91085e2022-02-10 18:05:45 +0100829 open_chan_parser = argparse.ArgumentParser()
830 open_chan_parser.add_argument(
831 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200832
Harald Weltec91085e2022-02-10 18:05:45 +0100833 @cmd2.with_argparser(open_chan_parser)
834 def do_open_channel(self, opts):
835 """Open a logical channel."""
836 (data, sw) = self._cmd.card._scc.manage_channel(
837 mode='open', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200838
Harald Weltec91085e2022-02-10 18:05:45 +0100839 close_chan_parser = argparse.ArgumentParser()
840 close_chan_parser.add_argument(
841 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200842
Harald Weltec91085e2022-02-10 18:05:45 +0100843 @cmd2.with_argparser(close_chan_parser)
844 def do_close_channel(self, opts):
845 """Close a logical channel."""
846 (data, sw) = self._cmd.card._scc.manage_channel(
847 mode='close', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200848
Harald Weltec91085e2022-02-10 18:05:45 +0100849 def do_status(self, opts):
850 """Perform the STATUS command."""
851 fcp_dec = self._cmd.rs.status()
852 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200853
Harald Weltec91085e2022-02-10 18:05:45 +0100854 suspend_uicc_parser = argparse.ArgumentParser()
855 suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60,
856 help='Proposed minimum duration of suspension')
857 suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60,
858 help='Proposed maximum duration of suspension')
Harald Welteec950532021-10-20 13:09:00 +0200859
Harald Weltec91085e2022-02-10 18:05:45 +0100860 # not ISO7816-4 but TS 102 221
861 @cmd2.with_argparser(suspend_uicc_parser)
862 def do_suspend_uicc(self, opts):
863 """Perform the SUSPEND UICC command. Only supported on some UICC."""
864 (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
865 max_len_secs=opts.max_duration_secs)
866 self._cmd.poutput(
867 'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
Harald Welteec950532021-10-20 13:09:00 +0200868
Harald Welte703f9332021-04-10 18:39:32 +0200869
Harald Weltef2e761c2021-04-11 11:56:44 +0200870option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
871 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200872argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200873
874global_group = option_parser.add_argument_group('General Options')
875global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200876 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100877global_group.add_argument('--csv', metavar='FILE',
878 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +0200879global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +0100880 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +0200881
882adm_group = global_group.add_mutually_exclusive_group()
883adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
884 help='ADM PIN used for provisioning (overwrites default)')
885adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
886 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +0100887
888
889if __name__ == '__main__':
890
Harald Weltec91085e2022-02-10 18:05:45 +0100891 # Parse options
892 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +0100893
Harald Weltec91085e2022-02-10 18:05:45 +0100894 # If a script file is specified, be sure that it actually exists
895 if opts.script:
896 if not os.access(opts.script, os.R_OK):
897 print("Invalid script file!")
898 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +0200899
Harald Weltec91085e2022-02-10 18:05:45 +0100900 # Register csv-file as card data provider, either from specified CSV
901 # or from CSV file in home directory
902 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
903 if opts.csv:
904 card_key_provider_register(CardKeyProviderCsv(opts.csv))
905 if os.path.isfile(csv_default):
906 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +0100907
Harald Weltec91085e2022-02-10 18:05:45 +0100908 # Init card reader driver
909 sl = init_reader(opts)
910 if sl is None:
911 exit(1)
Philipp Maierea95c392021-09-16 13:10:19 +0200912
Harald Weltec91085e2022-02-10 18:05:45 +0100913 # Create command layer
914 scc = SimCardCommands(transport=sl)
Philipp Maierea95c392021-09-16 13:10:19 +0200915
Harald Weltec91085e2022-02-10 18:05:45 +0100916 # Create a card handler (for bulk provisioning)
917 if opts.card_handler_config:
918 ch = CardHandlerAuto(None, opts.card_handler_config)
919 else:
920 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +0200921
Harald Weltec91085e2022-02-10 18:05:45 +0100922 # Detect and initialize the card in the reader. This may fail when there
923 # is no card in the reader or the card is unresponsive. PysimApp is
924 # able to tolerate and recover from that.
925 try:
926 rs, card = init_card(sl)
927 app = PysimApp(card, rs, sl, ch, opts.script)
928 except:
929 print("Card initialization failed with an exception:")
930 print("---------------------8<---------------------")
931 traceback.print_exc()
932 print("---------------------8<---------------------")
933 print("(you may still try to recover from this manually by using the 'equip' command.)")
934 print(
935 " it should also be noted that some readers may behave strangely when no card")
936 print(" is inserted.)")
937 print("")
938 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +0200939
Harald Weltec91085e2022-02-10 18:05:45 +0100940 # If the user supplies an ADM PIN at via commandline args authenticate
941 # immediately so that the user does not have to use the shell commands
942 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
943 if pin_adm:
944 if not card:
945 print("Card error, cannot do ADM verification with supplied ADM pin now.")
946 try:
947 card.verify_adm(h2b(pin_adm))
948 except Exception as e:
949 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +0100950
Harald Weltec91085e2022-02-10 18:05:45 +0100951 app.cmdloop()