blob: 95dc7073c17befe51a82c890e3e6ca24968c3a20 [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
Philipp Maiere7d1b672022-06-01 18:05:34 +020043from pySim.utils import h2b, swap_nibbles, rpad, b2h, h2s, JsonEncoder, bertlv_parse_one, sw_match
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!")
Philipp Maier7226c092022-06-01 17:58:38 +020094 return None, card
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:
Philipp Maier7226c092022-06-01 17:58:38 +0200227 if self.card:
228 self.prompt = 'pySIM-shell (no card profile)> '
229 else:
230 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100231
Harald Weltec91085e2022-02-10 18:05:45 +0100232 @cmd2.with_category(CUSTOM_CATEGORY)
233 def do_intro(self, _):
234 """Display the intro banner"""
235 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100236
Harald Weltec91085e2022-02-10 18:05:45 +0100237 def do_eof(self, _: argparse.Namespace) -> bool:
238 self.poutput("")
239 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200240
Harald Weltec91085e2022-02-10 18:05:45 +0100241 @cmd2.with_category(CUSTOM_CATEGORY)
242 def do_equip(self, opts):
243 """Equip pySim-shell with card"""
244 rs, card = init_card(sl)
245 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200246
Philipp Maier7226c092022-06-01 17:58:38 +0200247 apdu_cmd_parser = argparse.ArgumentParser()
248 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
Philipp Maiere7d1b672022-06-01 18:05:34 +0200249 apdu_cmd_parser.add_argument('--expect-sw', help='expect a specified status word', type=str, default=None)
Philipp Maier7226c092022-06-01 17:58:38 +0200250
251 @cmd2.with_argparser(apdu_cmd_parser)
252 def do_apdu(self, opts):
253 """Send a raw APDU to the card, and print SW + Response.
254 DANGEROUS: pySim-shell will not know any card state changes, and
255 not continue to work as expected if you e.g. select a different
256 file."""
257 data, sw = self.card._scc._tp.send_apdu(opts.APDU)
258 if data:
259 self.poutput("SW: %s, RESP: %s" % (sw, data))
260 else:
261 self.poutput("SW: %s" % sw)
Philipp Maiere7d1b672022-06-01 18:05:34 +0200262 if opts.expect_sw:
263 if not sw_match(sw, opts.expect_sw):
264 raise SwMatchError(sw, opts.expect_sw)
Philipp Maier7226c092022-06-01 17:58:38 +0200265
Harald Weltec91085e2022-02-10 18:05:45 +0100266 class InterceptStderr(list):
267 def __init__(self):
268 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200269
Harald Weltec91085e2022-02-10 18:05:45 +0100270 def __enter__(self):
271 self._stringio_stderr = StringIO()
272 sys.stderr = self._stringio_stderr
273 return self
Philipp Maier76667642021-09-22 16:53:22 +0200274
Harald Weltec91085e2022-02-10 18:05:45 +0100275 def __exit__(self, *args):
276 self.stderr = self._stringio_stderr.getvalue().strip()
277 del self._stringio_stderr
278 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200279
Harald Weltec91085e2022-02-10 18:05:45 +0100280 def _show_failure_sign(self):
281 self.poutput(style(" +-------------+", fg=fg.bright_red))
282 self.poutput(style(" + ## ## +", fg=fg.bright_red))
283 self.poutput(style(" + ## ## +", fg=fg.bright_red))
284 self.poutput(style(" + ### +", fg=fg.bright_red))
285 self.poutput(style(" + ## ## +", fg=fg.bright_red))
286 self.poutput(style(" + ## ## +", fg=fg.bright_red))
287 self.poutput(style(" +-------------+", fg=fg.bright_red))
288 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200289
Harald Weltec91085e2022-02-10 18:05:45 +0100290 def _show_success_sign(self):
291 self.poutput(style(" +-------------+", fg=fg.bright_green))
292 self.poutput(style(" + ## +", fg=fg.bright_green))
293 self.poutput(style(" + ## +", fg=fg.bright_green))
294 self.poutput(style(" + # ## +", fg=fg.bright_green))
295 self.poutput(style(" + ## # +", fg=fg.bright_green))
296 self.poutput(style(" + ## +", fg=fg.bright_green))
297 self.poutput(style(" +-------------+", fg=fg.bright_green))
298 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200299
Harald Weltec91085e2022-02-10 18:05:45 +0100300 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200301
Harald Weltec91085e2022-02-10 18:05:45 +0100302 # Early phase of card initialzation (this part may fail with an exception)
303 try:
304 rs, card = init_card(self.sl)
305 rc = self.equip(card, rs)
306 except:
307 self.poutput("")
308 self.poutput("Card initialization failed with an exception:")
309 self.poutput("---------------------8<---------------------")
310 traceback.print_exc()
311 self.poutput("---------------------8<---------------------")
312 self.poutput("")
313 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200314
Harald Weltec91085e2022-02-10 18:05:45 +0100315 # Actual card processing step. This part should never fail with an exception since the cmd2
316 # do_run_script method will catch any exception that might occur during script execution.
317 if rc:
318 self.poutput("")
319 self.poutput("Transcript stdout:")
320 self.poutput("---------------------8<---------------------")
321 with self.InterceptStderr() as logged:
322 self.do_run_script(script_path)
323 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200324
Harald Weltec91085e2022-02-10 18:05:45 +0100325 self.poutput("")
326 self.poutput("Transcript stderr:")
327 if logged.stderr:
328 self.poutput("---------------------8<---------------------")
329 self.poutput(logged.stderr)
330 self.poutput("---------------------8<---------------------")
331 else:
332 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200333
Harald Weltec91085e2022-02-10 18:05:45 +0100334 # Check for exceptions
335 self.poutput("")
336 if "EXCEPTION of type" not in logged.stderr:
337 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200338
Harald Weltec91085e2022-02-10 18:05:45 +0100339 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200340
Harald Weltec91085e2022-02-10 18:05:45 +0100341 bulk_script_parser = argparse.ArgumentParser()
342 bulk_script_parser.add_argument(
343 'script_path', help="path to the script file")
344 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
345 action='store_true')
346 bulk_script_parser.add_argument('--tries', type=int, default=2,
347 help='how many tries before trying the next card')
348 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
349 help='commandline to execute when card handling has stopped')
350 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
351 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200352
Harald Weltec91085e2022-02-10 18:05:45 +0100353 @cmd2.with_argparser(bulk_script_parser)
354 @cmd2.with_category(CUSTOM_CATEGORY)
355 def do_bulk_script(self, opts):
356 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200357
Harald Weltec91085e2022-02-10 18:05:45 +0100358 # Make sure that the script file exists and that it is readable.
359 if not os.access(opts.script_path, os.R_OK):
360 self.poutput("Invalid script file!")
361 return
Philipp Maier76667642021-09-22 16:53:22 +0200362
Harald Weltec91085e2022-02-10 18:05:45 +0100363 success_count = 0
364 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200365
Harald Weltec91085e2022-02-10 18:05:45 +0100366 first = True
367 while 1:
368 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
369 # The ratinale is: There may be a problem with the device, we do want to prevent that
370 # all remaining cards are fired to the error bin. This is only relevant for situations
371 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200372
Harald Weltec91085e2022-02-10 18:05:45 +0100373 try:
374 # In case of failure, try multiple times.
375 for i in range(opts.tries):
376 # fetch card into reader bay
377 ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200378
Harald Weltec91085e2022-02-10 18:05:45 +0100379 # if necessary execute an action before we start processing the card
380 if(opts.pre_card_action):
381 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200382
Harald Weltec91085e2022-02-10 18:05:45 +0100383 # process the card
384 rc = self._process_card(first, opts.script_path)
385 if rc == 0:
386 success_count = success_count + 1
387 self._show_success_sign()
388 self.poutput("Statistics: success :%i, failure: %i" % (
389 success_count, fail_count))
390 break
391 else:
392 fail_count = fail_count + 1
393 self._show_failure_sign()
394 self.poutput("Statistics: success :%i, failure: %i" % (
395 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200396
Harald Weltec91085e2022-02-10 18:05:45 +0100397 # Depending on success or failure, the card goes either in the "error" bin or in the
398 # "done" bin.
399 if rc < 0:
400 ch.error()
401 else:
402 ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200403
Harald Weltec91085e2022-02-10 18:05:45 +0100404 # In most cases it is possible to proceed with the next card, but the
405 # user may decide to halt immediately when an error occurs
406 if opts.halt_on_error and rc < 0:
407 return
Philipp Maier76667642021-09-22 16:53:22 +0200408
Harald Weltec91085e2022-02-10 18:05:45 +0100409 except (KeyboardInterrupt):
410 self.poutput("")
411 self.poutput("Terminated by user!")
412 return
413 except (SystemExit):
414 # When all cards are processed the card handler device will throw a SystemExit
415 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
416 # The user has the option to execute some action to make aware that the card handler
417 # needs service.
418 if(opts.on_stop_action):
419 os.system(opts.on_stop_action)
420 return
421 except:
422 self.poutput("")
423 self.poutput("Card handling failed with an exception:")
424 self.poutput("---------------------8<---------------------")
425 traceback.print_exc()
426 self.poutput("---------------------8<---------------------")
427 self.poutput("")
428 fail_count = fail_count + 1
429 self._show_failure_sign()
430 self.poutput("Statistics: success :%i, failure: %i" %
431 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200432
Harald Weltec91085e2022-02-10 18:05:45 +0100433 first = False
434
435 echo_parser = argparse.ArgumentParser()
436 echo_parser.add_argument('string', help="string to echo on the shell")
437
438 @cmd2.with_argparser(echo_parser)
439 @cmd2.with_category(CUSTOM_CATEGORY)
440 def do_echo(self, opts):
441 """Echo (print) a string on the console"""
442 self.poutput(opts.string)
443
Harald Welteb2edd142021-01-08 23:29:35 +0100444
Harald Welte31d2cf02021-04-03 10:47:29 +0200445@with_default_category('pySim Commands')
446class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100447 def __init__(self):
448 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100449
Harald Weltec91085e2022-02-10 18:05:45 +0100450 dir_parser = argparse.ArgumentParser()
451 dir_parser.add_argument(
452 '--fids', help='Show file identifiers', action='store_true')
453 dir_parser.add_argument(
454 '--names', help='Show file names', action='store_true')
455 dir_parser.add_argument(
456 '--apps', help='Show applications', action='store_true')
457 dir_parser.add_argument(
458 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100459
Harald Weltec91085e2022-02-10 18:05:45 +0100460 @cmd2.with_argparser(dir_parser)
461 def do_dir(self, opts):
462 """Show a listing of files available in currently selected DF or MF"""
463 if opts.all:
464 flags = []
465 elif opts.fids or opts.names or opts.apps:
466 flags = ['PARENT', 'SELF']
467 if opts.fids:
468 flags += ['FIDS', 'AIDS']
469 if opts.names:
470 flags += ['FNAMES', 'ANAMES']
471 if opts.apps:
472 flags += ['ANAMES', 'AIDS']
473 else:
474 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
475 selectables = list(
476 self._cmd.rs.selected_file.get_selectable_names(flags=flags))
477 directory_str = tabulate_str_list(
478 selectables, width=79, hspace=2, lspace=1, align_left=True)
479 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
480 self._cmd.poutput('/'.join(path_list))
481 path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
482 self._cmd.poutput('/'.join(path_list))
483 self._cmd.poutput(directory_str)
484 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100485
Philipp Maier9a4091d2022-05-19 10:20:30 +0200486 def walk(self, indent=0, action=None, context=None, **kwargs):
Harald Weltec91085e2022-02-10 18:05:45 +0100487 """Recursively walk through the file system, starting at the currently selected DF"""
488 files = self._cmd.rs.selected_file.get_selectables(
489 flags=['FNAMES', 'ANAMES'])
490 for f in files:
491 if not action:
492 output_str = " " * indent + str(f) + (" " * 250)
493 output_str = output_str[0:25]
494 if isinstance(files[f], CardADF):
495 output_str += " " + str(files[f].aid)
496 else:
497 output_str += " " + str(files[f].fid)
498 output_str += " " + str(files[f].desc)
499 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200500
Harald Weltec91085e2022-02-10 18:05:45 +0100501 if isinstance(files[f], CardDF):
502 skip_df = False
503 try:
504 fcp_dec = self._cmd.rs.select(f, self._cmd)
505 except Exception as e:
506 skip_df = True
507 df = self._cmd.rs.selected_file
508 df_path_list = df.fully_qualified_path(True)
509 df_skip_reason_str = '/'.join(df_path_list) + \
510 "/" + str(f) + ", " + str(e)
511 if context:
512 context['DF_SKIP'] += 1
513 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200514
Harald Weltec91085e2022-02-10 18:05:45 +0100515 # If the DF was skipped, we never have entered the directory
516 # below, so we must not move up.
517 if skip_df == False:
Philipp Maier9a4091d2022-05-19 10:20:30 +0200518 self.walk(indent + 1, action, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100519 fcp_dec = self._cmd.rs.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200520
Harald Weltec91085e2022-02-10 18:05:45 +0100521 elif action:
522 df_before_action = self._cmd.rs.selected_file
Philipp Maier9a4091d2022-05-19 10:20:30 +0200523 action(f, context, **kwargs)
Harald Weltec91085e2022-02-10 18:05:45 +0100524 # When walking through the file system tree the action must not
525 # always restore the currently selected file to the file that
526 # was selected before executing the action() callback.
527 if df_before_action != self._cmd.rs.selected_file:
528 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
529 % (str(self._cmd.rs.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100530
Harald Weltec91085e2022-02-10 18:05:45 +0100531 def do_tree(self, opts):
532 """Display a filesystem-tree with all selectable files"""
533 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100534
Philipp Maier9a4091d2022-05-19 10:20:30 +0200535 def export(self, filename, context, as_json):
Harald Weltec91085e2022-02-10 18:05:45 +0100536 """ Select and export a single file """
537 context['COUNT'] += 1
538 df = self._cmd.rs.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200539
Philipp Maierea81f752022-05-19 10:13:30 +0200540 # The currently selected file (not the file we are going to export)
541 # must always be an ADF or DF. From this starting point we select
542 # the EF we want to export. To maintain consistency we will then
543 # select the current DF again (see comment below).
Harald Weltec91085e2022-02-10 18:05:45 +0100544 if not isinstance(df, CardDF):
545 raise RuntimeError(
546 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200547
Harald Weltec91085e2022-02-10 18:05:45 +0100548 df_path_list = df.fully_qualified_path(True)
549 df_path_list_fid = df.fully_qualified_path(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100550
Harald Weltec91085e2022-02-10 18:05:45 +0100551 file_str = '/'.join(df_path_list) + "/" + str(filename)
552 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100553
Harald Weltec91085e2022-02-10 18:05:45 +0100554 self._cmd.poutput("# directory: %s (%s)" %
555 ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
556 try:
557 fcp_dec = self._cmd.rs.select(filename, self._cmd)
558 self._cmd.poutput("# file: %s (%s)" % (
559 self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100560
Harald Welte747a9782022-02-13 17:52:28 +0100561 structure = self._cmd.rs.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100562 self._cmd.poutput("# structure: %s" % str(structure))
Harald Welte2bb17f32022-02-15 15:41:55 +0100563 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.rs.selected_file_fcp_hex))
564 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.rs.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100565
Harald Weltec91085e2022-02-10 18:05:45 +0100566 for f in df_path_list:
567 self._cmd.poutput("select " + str(f))
568 self._cmd.poutput("select " + self._cmd.rs.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100569
Harald Weltec91085e2022-02-10 18:05:45 +0100570 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100571 if as_json:
572 result = self._cmd.rs.read_binary_dec()
573 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
574 else:
575 result = self._cmd.rs.read_binary()
576 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100577 elif structure == 'cyclic' or structure == 'linear_fixed':
578 # Use number of records specified in select response
Harald Welte747a9782022-02-13 17:52:28 +0100579 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
580 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100581 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100582 if as_json:
583 result = self._cmd.rs.read_record_dec(r)
584 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
585 else:
586 result = self._cmd.rs.read_record(r)
587 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
588
Harald Weltec91085e2022-02-10 18:05:45 +0100589 # When the select response does not return the number of records, read until we hit the
590 # first record that cannot be read.
591 else:
592 r = 1
593 while True:
594 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100595 if as_json:
596 result = self._cmd.rs.read_record_dec(r)
597 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
598 else:
599 result = self._cmd.rs.read_record(r)
600 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100601 except SwMatchError as e:
602 # We are past the last valid record - stop
603 if e.sw_actual == "9402":
604 break
605 # Some other problem occurred
606 else:
607 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100608 r = r + 1
609 elif structure == 'ber_tlv':
610 tags = self._cmd.rs.retrieve_tags()
611 for t in tags:
612 result = self._cmd.rs.retrieve_data(t)
613 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
614 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
615 else:
616 raise RuntimeError(
617 'Unsupported structure "%s" of file "%s"' % (structure, filename))
618 except Exception as e:
619 bad_file_str = '/'.join(df_path_list) + \
620 "/" + str(filename) + ", " + str(e)
621 self._cmd.poutput("# bad file: %s" % bad_file_str)
622 context['ERR'] += 1
623 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100624
Harald Weltec91085e2022-02-10 18:05:45 +0100625 # When reading the file is done, make sure the parent file is
626 # selected again. This will be the usual case, however we need
627 # to check before since we must not select the same DF twice
628 if df != self._cmd.rs.selected_file:
629 self._cmd.rs.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200630
Harald Weltec91085e2022-02-10 18:05:45 +0100631 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100632
Harald Weltec91085e2022-02-10 18:05:45 +0100633 export_parser = argparse.ArgumentParser()
634 export_parser.add_argument(
635 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100636 export_parser.add_argument(
637 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100638
Harald Weltec91085e2022-02-10 18:05:45 +0100639 @cmd2.with_argparser(export_parser)
640 def do_export(self, opts):
641 """Export files to script that can be imported back later"""
642 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
643 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
Philipp Maier9a4091d2022-05-19 10:20:30 +0200644 kwargs_export = {'as_json': opts.json}
Harald Weltec91085e2022-02-10 18:05:45 +0100645 if opts.filename:
Philipp Maier9a4091d2022-05-19 10:20:30 +0200646 self.export(opts.filename, context, **kwargs_export)
Harald Weltec91085e2022-02-10 18:05:45 +0100647 else:
Philipp Maier9a4091d2022-05-19 10:20:30 +0200648 self.walk(0, self.export, context, **kwargs_export)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200649
Harald Weltec91085e2022-02-10 18:05:45 +0100650 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200651
Harald Weltec91085e2022-02-10 18:05:45 +0100652 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
653 self._cmd.poutput("# bad files: %u" % context['ERR'])
654 for b in context['BAD']:
655 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200656
Harald Weltec91085e2022-02-10 18:05:45 +0100657 self._cmd.poutput("# skipped dedicated files(s): %u" %
658 context['DF_SKIP'])
659 for b in context['DF_SKIP_REASON']:
660 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200661
Harald Weltec91085e2022-02-10 18:05:45 +0100662 if context['ERR'] and context['DF_SKIP']:
663 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
664 context['ERR'], context['DF_SKIP']))
665 elif context['ERR']:
666 raise RuntimeError(
667 "unable to export %i elementary file(s)" % context['ERR'])
668 elif context['DF_SKIP']:
669 raise RuntimeError(
670 "unable to export %i dedicated files(s)" % context['ERR'])
Harald Welteb2edd142021-01-08 23:29:35 +0100671
Harald Weltec91085e2022-02-10 18:05:45 +0100672 def do_reset(self, opts):
673 """Reset the Card."""
674 atr = self._cmd.rs.reset(self._cmd)
675 self._cmd.poutput('Card ATR: %s' % atr)
676 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200677
Harald Weltec91085e2022-02-10 18:05:45 +0100678 def do_desc(self, opts):
679 """Display human readable file description for the currently selected file"""
680 desc = self._cmd.rs.selected_file.desc
681 if desc:
682 self._cmd.poutput(desc)
683 else:
684 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200685
Harald Weltec91085e2022-02-10 18:05:45 +0100686 def do_verify_adm(self, arg):
687 """VERIFY the ADM1 PIN"""
688 if arg:
689 # use specified ADM-PIN
690 pin_adm = sanitize_pin_adm(arg)
691 else:
692 # try to find an ADM-PIN if none is specified
693 result = card_key_provider_get_field(
694 'ADM1', key='ICCID', value=self._cmd.iccid)
695 pin_adm = sanitize_pin_adm(result)
696 if pin_adm:
697 self._cmd.poutput(
698 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
699 else:
700 raise ValueError(
701 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200702
Harald Weltec91085e2022-02-10 18:05:45 +0100703 if pin_adm:
704 self._cmd.card.verify_adm(h2b(pin_adm))
705 else:
706 raise ValueError("error: cannot authenticate, no adm-pin!")
707
Harald Welteb2edd142021-01-08 23:29:35 +0100708
Harald Welte31d2cf02021-04-03 10:47:29 +0200709@with_default_category('ISO7816 Commands')
710class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100711 def __init__(self):
712 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200713
Harald Weltec91085e2022-02-10 18:05:45 +0100714 def do_select(self, opts):
715 """SELECT a File (ADF/DF/EF)"""
716 if len(opts.arg_list) == 0:
717 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
718 path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
719 False)
720 self._cmd.poutput("currently selected file: " +
721 '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
722 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200723
Harald Weltec91085e2022-02-10 18:05:45 +0100724 path = opts.arg_list[0]
725 fcp_dec = self._cmd.rs.select(path, self._cmd)
726 self._cmd.update_prompt()
727 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200728
Harald Weltec91085e2022-02-10 18:05:45 +0100729 def complete_select(self, text, line, begidx, endidx) -> List[str]:
730 """Command Line tab completion for SELECT"""
731 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
732 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200733
Harald Weltec91085e2022-02-10 18:05:45 +0100734 def get_code(self, code):
735 """Use code either directly or try to get it from external data source"""
736 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200737
Harald Weltec91085e2022-02-10 18:05:45 +0100738 if str(code).upper() not in auto:
739 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200740
Harald Weltec91085e2022-02-10 18:05:45 +0100741 result = card_key_provider_get_field(
742 str(code), key='ICCID', value=self._cmd.iccid)
743 result = sanitize_pin_adm(result)
744 if result:
745 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
746 (code.upper(), result, self._cmd.iccid))
747 else:
748 self._cmd.poutput("cannot find %s for ICCID '%s'" %
749 (code.upper(), self._cmd.iccid))
750 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200751
Harald Weltec91085e2022-02-10 18:05:45 +0100752 verify_chv_parser = argparse.ArgumentParser()
753 verify_chv_parser.add_argument(
754 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
755 verify_chv_parser.add_argument(
756 '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 +0200757
Harald Weltec91085e2022-02-10 18:05:45 +0100758 @cmd2.with_argparser(verify_chv_parser)
759 def do_verify_chv(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100760 """Verify (authenticate) using specified CHV (PIN) code, which is how the specifications
761 call it if you authenticate yourself using the specified PIN. There usually is at least PIN1 and
762 PIN2."""
Harald Weltec91085e2022-02-10 18:05:45 +0100763 pin = self.get_code(opts.pin_code)
764 (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin))
765 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200766
Harald Weltec91085e2022-02-10 18:05:45 +0100767 unblock_chv_parser = argparse.ArgumentParser()
768 unblock_chv_parser.add_argument(
769 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
770 unblock_chv_parser.add_argument(
771 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
772 unblock_chv_parser.add_argument(
773 '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 +0200774
Harald Weltec91085e2022-02-10 18:05:45 +0100775 @cmd2.with_argparser(unblock_chv_parser)
776 def do_unblock_chv(self, opts):
777 """Unblock PIN code using specified PUK code"""
778 new_pin = self.get_code(opts.new_pin_code)
779 puk = self.get_code(opts.puk_code)
780 (data, sw) = self._cmd.card._scc.unblock_chv(
781 opts.pin_nr, h2b(puk), h2b(new_pin))
782 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 change_chv_parser = argparse.ArgumentParser()
785 change_chv_parser.add_argument(
786 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
787 change_chv_parser.add_argument(
788 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
789 change_chv_parser.add_argument(
790 '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 +0200791
Harald Weltec91085e2022-02-10 18:05:45 +0100792 @cmd2.with_argparser(change_chv_parser)
793 def do_change_chv(self, opts):
794 """Change PIN code to a new PIN code"""
795 new_pin = self.get_code(opts.new_pin_code)
796 pin = self.get_code(opts.pin_code)
797 (data, sw) = self._cmd.card._scc.change_chv(
798 opts.pin_nr, h2b(pin), h2b(new_pin))
799 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200800
Harald Weltec91085e2022-02-10 18:05:45 +0100801 disable_chv_parser = argparse.ArgumentParser()
802 disable_chv_parser.add_argument(
803 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
804 disable_chv_parser.add_argument(
805 '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 +0200806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 @cmd2.with_argparser(disable_chv_parser)
808 def do_disable_chv(self, opts):
809 """Disable PIN code using specified PIN code"""
810 pin = self.get_code(opts.pin_code)
811 (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin))
812 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200813
Harald Weltec91085e2022-02-10 18:05:45 +0100814 enable_chv_parser = argparse.ArgumentParser()
815 enable_chv_parser.add_argument(
816 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
817 enable_chv_parser.add_argument(
818 '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 +0200819
Harald Weltec91085e2022-02-10 18:05:45 +0100820 @cmd2.with_argparser(enable_chv_parser)
821 def do_enable_chv(self, opts):
822 """Enable PIN code using specified PIN code"""
823 pin = self.get_code(opts.pin_code)
824 (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin))
825 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200826
Harald Weltec91085e2022-02-10 18:05:45 +0100827 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100828 """Deactivate the currently selected EF"""
Harald Weltec91085e2022-02-10 18:05:45 +0100829 (data, sw) = self._cmd.card._scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200830
Harald Welte799c3542022-02-15 15:56:28 +0100831 activate_file_parser = argparse.ArgumentParser()
832 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
833 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100834 def do_activate_file(self, opts):
Harald Welte12af7932022-02-15 16:39:08 +0100835 """Activate the specified EF. This used to be called REHABILITATE in TS 11.11 for classic
836 SIM. You need to specify the name or FID of the file to activate."""
Harald Welte799c3542022-02-15 15:56:28 +0100837 (data, sw) = self._cmd.rs.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200838
Harald Weltec91085e2022-02-10 18:05:45 +0100839 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
840 """Command Line tab completion for ACTIVATE FILE"""
841 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
842 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200843
Harald Weltec91085e2022-02-10 18:05:45 +0100844 open_chan_parser = argparse.ArgumentParser()
845 open_chan_parser.add_argument(
846 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200847
Harald Weltec91085e2022-02-10 18:05:45 +0100848 @cmd2.with_argparser(open_chan_parser)
849 def do_open_channel(self, opts):
850 """Open a logical channel."""
851 (data, sw) = self._cmd.card._scc.manage_channel(
852 mode='open', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200853
Harald Weltec91085e2022-02-10 18:05:45 +0100854 close_chan_parser = argparse.ArgumentParser()
855 close_chan_parser.add_argument(
856 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200857
Harald Weltec91085e2022-02-10 18:05:45 +0100858 @cmd2.with_argparser(close_chan_parser)
859 def do_close_channel(self, opts):
860 """Close a logical channel."""
861 (data, sw) = self._cmd.card._scc.manage_channel(
862 mode='close', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200863
Harald Weltec91085e2022-02-10 18:05:45 +0100864 def do_status(self, opts):
865 """Perform the STATUS command."""
866 fcp_dec = self._cmd.rs.status()
867 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200868
Harald Weltec91085e2022-02-10 18:05:45 +0100869 suspend_uicc_parser = argparse.ArgumentParser()
870 suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60,
871 help='Proposed minimum duration of suspension')
872 suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60,
873 help='Proposed maximum duration of suspension')
Harald Welteec950532021-10-20 13:09:00 +0200874
Harald Weltec91085e2022-02-10 18:05:45 +0100875 # not ISO7816-4 but TS 102 221
876 @cmd2.with_argparser(suspend_uicc_parser)
877 def do_suspend_uicc(self, opts):
878 """Perform the SUSPEND UICC command. Only supported on some UICC."""
879 (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
880 max_len_secs=opts.max_duration_secs)
881 self._cmd.poutput(
882 'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
Harald Welteec950532021-10-20 13:09:00 +0200883
Harald Welte703f9332021-04-10 18:39:32 +0200884
Harald Weltef2e761c2021-04-11 11:56:44 +0200885option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
886 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200887argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200888
889global_group = option_parser.add_argument_group('General Options')
890global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200891 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100892global_group.add_argument('--csv', metavar='FILE',
893 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +0200894global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +0100895 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +0200896
897adm_group = global_group.add_mutually_exclusive_group()
898adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
899 help='ADM PIN used for provisioning (overwrites default)')
900adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
901 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +0100902
903
904if __name__ == '__main__':
905
Harald Weltec91085e2022-02-10 18:05:45 +0100906 # Parse options
907 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +0100908
Harald Weltec91085e2022-02-10 18:05:45 +0100909 # If a script file is specified, be sure that it actually exists
910 if opts.script:
911 if not os.access(opts.script, os.R_OK):
912 print("Invalid script file!")
913 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +0200914
Harald Weltec91085e2022-02-10 18:05:45 +0100915 # Register csv-file as card data provider, either from specified CSV
916 # or from CSV file in home directory
917 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
918 if opts.csv:
919 card_key_provider_register(CardKeyProviderCsv(opts.csv))
920 if os.path.isfile(csv_default):
921 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +0100922
Harald Weltec91085e2022-02-10 18:05:45 +0100923 # Init card reader driver
924 sl = init_reader(opts)
925 if sl is None:
926 exit(1)
Philipp Maierea95c392021-09-16 13:10:19 +0200927
Harald Weltec91085e2022-02-10 18:05:45 +0100928 # Create command layer
929 scc = SimCardCommands(transport=sl)
Philipp Maierea95c392021-09-16 13:10:19 +0200930
Harald Weltec91085e2022-02-10 18:05:45 +0100931 # Create a card handler (for bulk provisioning)
932 if opts.card_handler_config:
933 ch = CardHandlerAuto(None, opts.card_handler_config)
934 else:
935 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +0200936
Harald Weltec91085e2022-02-10 18:05:45 +0100937 # Detect and initialize the card in the reader. This may fail when there
938 # is no card in the reader or the card is unresponsive. PysimApp is
939 # able to tolerate and recover from that.
940 try:
941 rs, card = init_card(sl)
942 app = PysimApp(card, rs, sl, ch, opts.script)
943 except:
944 print("Card initialization failed with an exception:")
945 print("---------------------8<---------------------")
946 traceback.print_exc()
947 print("---------------------8<---------------------")
948 print("(you may still try to recover from this manually by using the 'equip' command.)")
949 print(
950 " it should also be noted that some readers may behave strangely when no card")
951 print(" is inserted.)")
952 print("")
Philipp Maier7226c092022-06-01 17:58:38 +0200953 app = PysimApp(card, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +0200954
Harald Weltec91085e2022-02-10 18:05:45 +0100955 # If the user supplies an ADM PIN at via commandline args authenticate
956 # immediately so that the user does not have to use the shell commands
957 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
958 if pin_adm:
959 if not card:
960 print("Card error, cannot do ADM verification with supplied ADM pin now.")
961 try:
962 card.verify_adm(h2b(pin_adm))
963 except Exception as e:
964 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +0100965
Harald Weltec91085e2022-02-10 18:05:45 +0100966 app.cmdloop()