blob: ec417c78b902ac579f4450a45dc9d87538872efe [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 Welte2a33ad22021-10-20 10:14:18 +020056from pySim.gsm_r import DF_EIRENE
Harald Welteb2edd142021-01-08 23:29:35 +010057
Harald Welte4c1dca02021-10-14 17:48:25 +020058# we need to import this module so that the SysmocomSJA2 sub-class of
59# CardModel is created, which will add the ATR-based matching and
60# calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models
Harald Weltef44256c2021-10-14 15:53:39 +020061import pySim.sysmocom_sja2
62
Harald Welte4442b3d2021-04-03 09:00:16 +020063from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010064
Harald Weltec91085e2022-02-10 18:05:45 +010065
Philipp Maierea95c392021-09-16 13:10:19 +020066def init_card(sl):
Harald Weltec91085e2022-02-10 18:05:45 +010067 """
68 Detect card in reader and setup card profile and runtime state. This
69 function must be called at least once on startup. The card and runtime
70 state object (rs) is required for all pySim-shell commands.
71 """
Philipp Maierea95c392021-09-16 13:10:19 +020072
Harald Weltec91085e2022-02-10 18:05:45 +010073 # Wait up to three seconds for a card in reader and try to detect
74 # the card type.
75 print("Waiting for card...")
76 try:
77 sl.wait_for_card(3)
78 except NoCardError:
79 print("No card detected!")
80 return None, None
81 except:
82 print("Card not readable!")
83 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020084
Harald Weltec91085e2022-02-10 18:05:45 +010085 card = card_detect("auto", scc)
86 if card is None:
87 print("Warning: Could not detect card type - assuming a generic card type...")
88 card = SimCard(scc)
Philipp Maiera028c7d2021-11-08 16:12:03 +010089
Harald Weltec91085e2022-02-10 18:05:45 +010090 profile = CardProfile.pick(scc)
91 if profile is None:
92 print("Unsupported card type!")
93 return None, None
Philipp Maierea95c392021-09-16 13:10:19 +020094
Harald Weltec91085e2022-02-10 18:05:45 +010095 print("Info: Card is of type: %s" % str(profile))
Philipp Maiera028c7d2021-11-08 16:12:03 +010096
Harald Weltec91085e2022-02-10 18:05:45 +010097 # FIXME: This shouln't be here, the profile should add the applications,
98 # however, we cannot simply put his into ts_102_221.py since we would
99 # have to e.g. import CardApplicationUSIM from ts_31_102.py, which already
100 # imports from ts_102_221.py. This means we will end up with a circular
101 # import, which needs to be resolved first.
102 if isinstance(profile, CardProfileUICC):
103 profile.add_application(CardApplicationUSIM())
104 profile.add_application(CardApplicationISIM())
105 profile.add_application(CardApplicationARAM())
Philipp Maiera028c7d2021-11-08 16:12:03 +0100106
Harald Weltec91085e2022-02-10 18:05:45 +0100107 # Create runtime state with card profile
108 rs = RuntimeState(card, profile)
Philipp Maierea95c392021-09-16 13:10:19 +0200109
Harald Weltec91085e2022-02-10 18:05:45 +0100110 # FIXME: This is an GSM-R related file, it needs to be added throughout,
111 # the profile. At the moment we add it for all cards, this won't hurt,
112 # but regular SIM and UICC will not have it and fail to select it.
113 rs.mf.add_file(DF_EIRENE())
Philipp Maierea95c392021-09-16 13:10:19 +0200114
Harald Weltec91085e2022-02-10 18:05:45 +0100115 CardModel.apply_matching_models(scc, rs)
Harald Weltef44256c2021-10-14 15:53:39 +0200116
Harald Weltec91085e2022-02-10 18:05:45 +0100117 # inform the transport that we can do context-specific SW interpretation
118 sl.set_sw_interpreter(rs)
Philipp Maierea95c392021-09-16 13:10:19 +0200119
Harald Weltec91085e2022-02-10 18:05:45 +0100120 return rs, card
121
Philipp Maier2b11c322021-03-17 12:37:39 +0100122
Harald Welteb2edd142021-01-08 23:29:35 +0100123class PysimApp(cmd2.Cmd):
Harald Weltec91085e2022-02-10 18:05:45 +0100124 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier76667642021-09-22 16:53:22 +0200125
Harald Weltec91085e2022-02-10 18:05:45 +0100126 def __init__(self, card, rs, sl, ch, script=None):
127 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
128 use_ipython=True, auto_load_commands=False, startup_script=script)
129 self.intro = style('Welcome to pySim-shell!', fg=fg.red)
130 self.default_category = 'pySim-shell built-in commands'
131 self.card = None
132 self.rs = None
133 self.py_locals = {'card': self.card, 'rs': self.rs}
134 self.sl = sl
135 self.ch = ch
Harald Welte1748b932021-04-06 21:12:25 +0200136
Harald Weltec91085e2022-02-10 18:05:45 +0100137 self.numeric_path = False
138 self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
139 onchange_cb=self._onchange_numeric_path))
140 self.conserve_write = True
141 self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
142 onchange_cb=self._onchange_conserve_write))
143 self.json_pretty_print = True
144 self.add_settable(cmd2.Settable('json_pretty_print',
145 bool, 'Pretty-Print JSON output'))
146 self.apdu_trace = False
147 self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
148 onchange_cb=self._onchange_apdu_trace))
Philipp Maier5d698e52021-09-16 13:18:01 +0200149
Harald Weltec91085e2022-02-10 18:05:45 +0100150 self.equip(card, rs)
Philipp Maier5d698e52021-09-16 13:18:01 +0200151
Harald Weltec91085e2022-02-10 18:05:45 +0100152 def equip(self, card, rs):
153 """
154 Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and
155 and commands to enable card operations.
156 """
Philipp Maier76667642021-09-22 16:53:22 +0200157
Harald Weltec91085e2022-02-10 18:05:45 +0100158 rc = False
Philipp Maier5d698e52021-09-16 13:18:01 +0200159
Harald Weltec91085e2022-02-10 18:05:45 +0100160 # Unequip everything from pySim-shell that would not work in unequipped state
161 if self.rs:
162 self.rs.unregister_cmds(self)
163 for cmds in [Iso7816Commands, PySimCommands]:
164 cmd_set = self.find_commandsets(cmds)
165 if cmd_set:
166 self.unregister_command_set(cmd_set[0])
Philipp Maier5d698e52021-09-16 13:18:01 +0200167
Harald Weltec91085e2022-02-10 18:05:45 +0100168 self.card = card
169 self.rs = rs
Philipp Maier5d698e52021-09-16 13:18:01 +0200170
Harald Weltec91085e2022-02-10 18:05:45 +0100171 # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
172 # needed to operate on cards.
173 if self.card and self.rs:
174 self._onchange_conserve_write(
175 'conserve_write', False, self.conserve_write)
176 self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
177 self.register_command_set(Iso7816Commands())
Harald Welte3c9b7842021-10-19 21:44:24 +0200178 self.register_command_set(Ts102222Commands())
Harald Weltec91085e2022-02-10 18:05:45 +0100179 self.register_command_set(PySimCommands())
180 self.iccid, sw = self.card.read_iccid()
181 rs.select('MF', self)
182 rc = True
183 else:
184 self.poutput("pySim-shell not equipped!")
Philipp Maier5d698e52021-09-16 13:18:01 +0200185
Harald Weltec91085e2022-02-10 18:05:45 +0100186 self.update_prompt()
187 return rc
Harald Welteb2edd142021-01-08 23:29:35 +0100188
Harald Weltec91085e2022-02-10 18:05:45 +0100189 def poutput_json(self, data, force_no_pretty=False):
190 """like cmd2.poutput() but for a JSON serializable dict."""
191 if force_no_pretty or self.json_pretty_print == False:
192 output = json.dumps(data, cls=JsonEncoder)
193 else:
194 output = json.dumps(data, cls=JsonEncoder, indent=4)
195 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +0100196
Harald Weltec91085e2022-02-10 18:05:45 +0100197 def _onchange_numeric_path(self, param_name, old, new):
198 self.update_prompt()
Philipp Maier38c74f62021-03-17 17:19:52 +0100199
Harald Weltec91085e2022-02-10 18:05:45 +0100200 def _onchange_conserve_write(self, param_name, old, new):
201 if self.rs:
202 self.rs.conserve_write = new
Harald Welte7829d8a2021-04-10 11:28:53 +0200203
Harald Weltec91085e2022-02-10 18:05:45 +0100204 def _onchange_apdu_trace(self, param_name, old, new):
205 if self.card:
206 if new == True:
207 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
208 else:
209 self.card._scc._tp.apdu_tracer = None
Harald Welte7829d8a2021-04-10 11:28:53 +0200210
Harald Weltec91085e2022-02-10 18:05:45 +0100211 class Cmd2ApduTracer(ApduTracer):
212 def __init__(self, cmd2_app):
213 self.cmd2 = app
Harald Welte7829d8a2021-04-10 11:28:53 +0200214
Harald Weltec91085e2022-02-10 18:05:45 +0100215 def trace_response(self, cmd, sw, resp):
216 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
217 self.cmd2.poutput("<- %s: %s" % (sw, resp))
Harald Welteb2edd142021-01-08 23:29:35 +0100218
Harald Weltec91085e2022-02-10 18:05:45 +0100219 def update_prompt(self):
220 if self.rs:
221 path_list = self.rs.selected_file.fully_qualified_path(
222 not self.numeric_path)
223 self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
224 else:
225 self.prompt = 'pySIM-shell (no card)> '
Harald Welteb2edd142021-01-08 23:29:35 +0100226
Harald Weltec91085e2022-02-10 18:05:45 +0100227 @cmd2.with_category(CUSTOM_CATEGORY)
228 def do_intro(self, _):
229 """Display the intro banner"""
230 self.poutput(self.intro)
Philipp Maier9764de22021-11-03 10:44:39 +0100231
Harald Weltec91085e2022-02-10 18:05:45 +0100232 def do_eof(self, _: argparse.Namespace) -> bool:
233 self.poutput("")
234 return self.do_quit('')
Philipp Maier5d698e52021-09-16 13:18:01 +0200235
Harald Weltec91085e2022-02-10 18:05:45 +0100236 @cmd2.with_category(CUSTOM_CATEGORY)
237 def do_equip(self, opts):
238 """Equip pySim-shell with card"""
239 rs, card = init_card(sl)
240 self.equip(card, rs)
Philipp Maier76667642021-09-22 16:53:22 +0200241
Harald Weltec91085e2022-02-10 18:05:45 +0100242 class InterceptStderr(list):
243 def __init__(self):
244 self._stderr_backup = sys.stderr
Philipp Maier76667642021-09-22 16:53:22 +0200245
Harald Weltec91085e2022-02-10 18:05:45 +0100246 def __enter__(self):
247 self._stringio_stderr = StringIO()
248 sys.stderr = self._stringio_stderr
249 return self
Philipp Maier76667642021-09-22 16:53:22 +0200250
Harald Weltec91085e2022-02-10 18:05:45 +0100251 def __exit__(self, *args):
252 self.stderr = self._stringio_stderr.getvalue().strip()
253 del self._stringio_stderr
254 sys.stderr = self._stderr_backup
Philipp Maier76667642021-09-22 16:53:22 +0200255
Harald Weltec91085e2022-02-10 18:05:45 +0100256 def _show_failure_sign(self):
257 self.poutput(style(" +-------------+", fg=fg.bright_red))
258 self.poutput(style(" + ## ## +", fg=fg.bright_red))
259 self.poutput(style(" + ## ## +", fg=fg.bright_red))
260 self.poutput(style(" + ### +", fg=fg.bright_red))
261 self.poutput(style(" + ## ## +", fg=fg.bright_red))
262 self.poutput(style(" + ## ## +", fg=fg.bright_red))
263 self.poutput(style(" +-------------+", fg=fg.bright_red))
264 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200265
Harald Weltec91085e2022-02-10 18:05:45 +0100266 def _show_success_sign(self):
267 self.poutput(style(" +-------------+", fg=fg.bright_green))
268 self.poutput(style(" + ## +", fg=fg.bright_green))
269 self.poutput(style(" + ## +", fg=fg.bright_green))
270 self.poutput(style(" + # ## +", fg=fg.bright_green))
271 self.poutput(style(" + ## # +", fg=fg.bright_green))
272 self.poutput(style(" + ## +", fg=fg.bright_green))
273 self.poutput(style(" +-------------+", fg=fg.bright_green))
274 self.poutput("")
Philipp Maier76667642021-09-22 16:53:22 +0200275
Harald Weltec91085e2022-02-10 18:05:45 +0100276 def _process_card(self, first, script_path):
Philipp Maier76667642021-09-22 16:53:22 +0200277
Harald Weltec91085e2022-02-10 18:05:45 +0100278 # Early phase of card initialzation (this part may fail with an exception)
279 try:
280 rs, card = init_card(self.sl)
281 rc = self.equip(card, rs)
282 except:
283 self.poutput("")
284 self.poutput("Card initialization failed with an exception:")
285 self.poutput("---------------------8<---------------------")
286 traceback.print_exc()
287 self.poutput("---------------------8<---------------------")
288 self.poutput("")
289 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200290
Harald Weltec91085e2022-02-10 18:05:45 +0100291 # Actual card processing step. This part should never fail with an exception since the cmd2
292 # do_run_script method will catch any exception that might occur during script execution.
293 if rc:
294 self.poutput("")
295 self.poutput("Transcript stdout:")
296 self.poutput("---------------------8<---------------------")
297 with self.InterceptStderr() as logged:
298 self.do_run_script(script_path)
299 self.poutput("---------------------8<---------------------")
Philipp Maier76667642021-09-22 16:53:22 +0200300
Harald Weltec91085e2022-02-10 18:05:45 +0100301 self.poutput("")
302 self.poutput("Transcript stderr:")
303 if logged.stderr:
304 self.poutput("---------------------8<---------------------")
305 self.poutput(logged.stderr)
306 self.poutput("---------------------8<---------------------")
307 else:
308 self.poutput("(none)")
Philipp Maier76667642021-09-22 16:53:22 +0200309
Harald Weltec91085e2022-02-10 18:05:45 +0100310 # Check for exceptions
311 self.poutput("")
312 if "EXCEPTION of type" not in logged.stderr:
313 return 0
Philipp Maier76667642021-09-22 16:53:22 +0200314
Harald Weltec91085e2022-02-10 18:05:45 +0100315 return -1
Philipp Maier76667642021-09-22 16:53:22 +0200316
Harald Weltec91085e2022-02-10 18:05:45 +0100317 bulk_script_parser = argparse.ArgumentParser()
318 bulk_script_parser.add_argument(
319 'script_path', help="path to the script file")
320 bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
321 action='store_true')
322 bulk_script_parser.add_argument('--tries', type=int, default=2,
323 help='how many tries before trying the next card')
324 bulk_script_parser.add_argument('--on_stop_action', type=str, default=None,
325 help='commandline to execute when card handling has stopped')
326 bulk_script_parser.add_argument('--pre_card_action', type=str, default=None,
327 help='commandline to execute before actually talking to the card')
Philipp Maier76667642021-09-22 16:53:22 +0200328
Harald Weltec91085e2022-02-10 18:05:45 +0100329 @cmd2.with_argparser(bulk_script_parser)
330 @cmd2.with_category(CUSTOM_CATEGORY)
331 def do_bulk_script(self, opts):
332 """Run script on multiple cards (bulk provisioning)"""
Philipp Maier76667642021-09-22 16:53:22 +0200333
Harald Weltec91085e2022-02-10 18:05:45 +0100334 # Make sure that the script file exists and that it is readable.
335 if not os.access(opts.script_path, os.R_OK):
336 self.poutput("Invalid script file!")
337 return
Philipp Maier76667642021-09-22 16:53:22 +0200338
Harald Weltec91085e2022-02-10 18:05:45 +0100339 success_count = 0
340 fail_count = 0
Philipp Maier76667642021-09-22 16:53:22 +0200341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 first = True
343 while 1:
344 # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop.
345 # The ratinale is: There may be a problem with the device, we do want to prevent that
346 # all remaining cards are fired to the error bin. This is only relevant for situations
347 # with large stacks, probably we do not need this feature right now.
Philipp Maier76667642021-09-22 16:53:22 +0200348
Harald Weltec91085e2022-02-10 18:05:45 +0100349 try:
350 # In case of failure, try multiple times.
351 for i in range(opts.tries):
352 # fetch card into reader bay
353 ch.get(first)
Philipp Maier76667642021-09-22 16:53:22 +0200354
Harald Weltec91085e2022-02-10 18:05:45 +0100355 # if necessary execute an action before we start processing the card
356 if(opts.pre_card_action):
357 os.system(opts.pre_card_action)
Philipp Maier76667642021-09-22 16:53:22 +0200358
Harald Weltec91085e2022-02-10 18:05:45 +0100359 # process the card
360 rc = self._process_card(first, opts.script_path)
361 if rc == 0:
362 success_count = success_count + 1
363 self._show_success_sign()
364 self.poutput("Statistics: success :%i, failure: %i" % (
365 success_count, fail_count))
366 break
367 else:
368 fail_count = fail_count + 1
369 self._show_failure_sign()
370 self.poutput("Statistics: success :%i, failure: %i" % (
371 success_count, fail_count))
Philipp Maier76667642021-09-22 16:53:22 +0200372
Harald Weltec91085e2022-02-10 18:05:45 +0100373 # Depending on success or failure, the card goes either in the "error" bin or in the
374 # "done" bin.
375 if rc < 0:
376 ch.error()
377 else:
378 ch.done()
Philipp Maier76667642021-09-22 16:53:22 +0200379
Harald Weltec91085e2022-02-10 18:05:45 +0100380 # In most cases it is possible to proceed with the next card, but the
381 # user may decide to halt immediately when an error occurs
382 if opts.halt_on_error and rc < 0:
383 return
Philipp Maier76667642021-09-22 16:53:22 +0200384
Harald Weltec91085e2022-02-10 18:05:45 +0100385 except (KeyboardInterrupt):
386 self.poutput("")
387 self.poutput("Terminated by user!")
388 return
389 except (SystemExit):
390 # When all cards are processed the card handler device will throw a SystemExit
391 # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here.
392 # The user has the option to execute some action to make aware that the card handler
393 # needs service.
394 if(opts.on_stop_action):
395 os.system(opts.on_stop_action)
396 return
397 except:
398 self.poutput("")
399 self.poutput("Card handling failed with an exception:")
400 self.poutput("---------------------8<---------------------")
401 traceback.print_exc()
402 self.poutput("---------------------8<---------------------")
403 self.poutput("")
404 fail_count = fail_count + 1
405 self._show_failure_sign()
406 self.poutput("Statistics: success :%i, failure: %i" %
407 (success_count, fail_count))
Philipp Maierb52feed2021-09-22 16:43:13 +0200408
Harald Weltec91085e2022-02-10 18:05:45 +0100409 first = False
410
411 echo_parser = argparse.ArgumentParser()
412 echo_parser.add_argument('string', help="string to echo on the shell")
413
414 @cmd2.with_argparser(echo_parser)
415 @cmd2.with_category(CUSTOM_CATEGORY)
416 def do_echo(self, opts):
417 """Echo (print) a string on the console"""
418 self.poutput(opts.string)
419
Harald Welteb2edd142021-01-08 23:29:35 +0100420
Harald Welte31d2cf02021-04-03 10:47:29 +0200421@with_default_category('pySim Commands')
422class PySimCommands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100423 def __init__(self):
424 super().__init__()
Harald Welteb2edd142021-01-08 23:29:35 +0100425
Harald Weltec91085e2022-02-10 18:05:45 +0100426 dir_parser = argparse.ArgumentParser()
427 dir_parser.add_argument(
428 '--fids', help='Show file identifiers', action='store_true')
429 dir_parser.add_argument(
430 '--names', help='Show file names', action='store_true')
431 dir_parser.add_argument(
432 '--apps', help='Show applications', action='store_true')
433 dir_parser.add_argument(
434 '--all', help='Show all selectable identifiers and names', action='store_true')
Philipp Maier5d3e2592021-02-22 17:22:16 +0100435
Harald Weltec91085e2022-02-10 18:05:45 +0100436 @cmd2.with_argparser(dir_parser)
437 def do_dir(self, opts):
438 """Show a listing of files available in currently selected DF or MF"""
439 if opts.all:
440 flags = []
441 elif opts.fids or opts.names or opts.apps:
442 flags = ['PARENT', 'SELF']
443 if opts.fids:
444 flags += ['FIDS', 'AIDS']
445 if opts.names:
446 flags += ['FNAMES', 'ANAMES']
447 if opts.apps:
448 flags += ['ANAMES', 'AIDS']
449 else:
450 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
451 selectables = list(
452 self._cmd.rs.selected_file.get_selectable_names(flags=flags))
453 directory_str = tabulate_str_list(
454 selectables, width=79, hspace=2, lspace=1, align_left=True)
455 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
456 self._cmd.poutput('/'.join(path_list))
457 path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
458 self._cmd.poutput('/'.join(path_list))
459 self._cmd.poutput(directory_str)
460 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100461
Harald Welte08b11ab2022-02-10 18:56:41 +0100462 def walk(self, indent=0, action=None, context=None, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100463 """Recursively walk through the file system, starting at the currently selected DF"""
464 files = self._cmd.rs.selected_file.get_selectables(
465 flags=['FNAMES', 'ANAMES'])
466 for f in files:
467 if not action:
468 output_str = " " * indent + str(f) + (" " * 250)
469 output_str = output_str[0:25]
470 if isinstance(files[f], CardADF):
471 output_str += " " + str(files[f].aid)
472 else:
473 output_str += " " + str(files[f].fid)
474 output_str += " " + str(files[f].desc)
475 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200476
Harald Weltec91085e2022-02-10 18:05:45 +0100477 if isinstance(files[f], CardDF):
478 skip_df = False
479 try:
480 fcp_dec = self._cmd.rs.select(f, self._cmd)
481 except Exception as e:
482 skip_df = True
483 df = self._cmd.rs.selected_file
484 df_path_list = df.fully_qualified_path(True)
485 df_skip_reason_str = '/'.join(df_path_list) + \
486 "/" + str(f) + ", " + str(e)
487 if context:
488 context['DF_SKIP'] += 1
489 context['DF_SKIP_REASON'].append(df_skip_reason_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200490
Harald Weltec91085e2022-02-10 18:05:45 +0100491 # If the DF was skipped, we never have entered the directory
492 # below, so we must not move up.
493 if skip_df == False:
Harald Welte08b11ab2022-02-10 18:56:41 +0100494 self.walk(indent + 1, action, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100495 fcp_dec = self._cmd.rs.select("..", self._cmd)
Philipp Maierf408a402021-04-09 21:16:12 +0200496
Harald Weltec91085e2022-02-10 18:05:45 +0100497 elif action:
498 df_before_action = self._cmd.rs.selected_file
Harald Welte08b11ab2022-02-10 18:56:41 +0100499 action(f, context, as_json)
Harald Weltec91085e2022-02-10 18:05:45 +0100500 # When walking through the file system tree the action must not
501 # always restore the currently selected file to the file that
502 # was selected before executing the action() callback.
503 if df_before_action != self._cmd.rs.selected_file:
504 raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected"
505 % (str(self._cmd.rs.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100506
Harald Weltec91085e2022-02-10 18:05:45 +0100507 def do_tree(self, opts):
508 """Display a filesystem-tree with all selectable files"""
509 self.walk()
Philipp Maierff9dae22021-02-25 17:03:21 +0100510
Harald Welte08b11ab2022-02-10 18:56:41 +0100511 def export(self, filename, context, as_json=False):
Harald Weltec91085e2022-02-10 18:05:45 +0100512 """ Select and export a single file """
513 context['COUNT'] += 1
514 df = self._cmd.rs.selected_file
Philipp Maierac34dcc2021-04-01 17:19:05 +0200515
Harald Weltec91085e2022-02-10 18:05:45 +0100516 if not isinstance(df, CardDF):
517 raise RuntimeError(
518 "currently selected file %s is not a DF or ADF" % str(df))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200519
Harald Weltec91085e2022-02-10 18:05:45 +0100520 df_path_list = df.fully_qualified_path(True)
521 df_path_list_fid = df.fully_qualified_path(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100522
Harald Weltec91085e2022-02-10 18:05:45 +0100523 file_str = '/'.join(df_path_list) + "/" + str(filename)
524 self._cmd.poutput(boxed_heading_str(file_str))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100525
Harald Weltec91085e2022-02-10 18:05:45 +0100526 self._cmd.poutput("# directory: %s (%s)" %
527 ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
528 try:
529 fcp_dec = self._cmd.rs.select(filename, self._cmd)
530 self._cmd.poutput("# file: %s (%s)" % (
531 self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100532
Harald Welte747a9782022-02-13 17:52:28 +0100533 structure = self._cmd.rs.selected_file_structure()
Harald Weltec91085e2022-02-10 18:05:45 +0100534 self._cmd.poutput("# structure: %s" % str(structure))
Harald Welte2bb17f32022-02-15 15:41:55 +0100535 self._cmd.poutput("# RAW FCP Template: %s" % str(self._cmd.rs.selected_file_fcp_hex))
536 self._cmd.poutput("# Decoded FCP Template: %s" % str(self._cmd.rs.selected_file_fcp))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100537
Harald Weltec91085e2022-02-10 18:05:45 +0100538 for f in df_path_list:
539 self._cmd.poutput("select " + str(f))
540 self._cmd.poutput("select " + self._cmd.rs.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100541
Harald Weltec91085e2022-02-10 18:05:45 +0100542 if structure == 'transparent':
Harald Welte08b11ab2022-02-10 18:56:41 +0100543 if as_json:
544 result = self._cmd.rs.read_binary_dec()
545 self._cmd.poutput("update_binary_decoded '%s'" % json.dumps(result[0], cls=JsonEncoder))
546 else:
547 result = self._cmd.rs.read_binary()
548 self._cmd.poutput("update_binary " + str(result[0]))
Harald Weltec91085e2022-02-10 18:05:45 +0100549 elif structure == 'cyclic' or structure == 'linear_fixed':
550 # Use number of records specified in select response
Harald Welte747a9782022-02-13 17:52:28 +0100551 num_of_rec = self._cmd.rs.selected_file_num_of_rec()
552 if num_of_rec:
Harald Weltec91085e2022-02-10 18:05:45 +0100553 for r in range(1, num_of_rec + 1):
Harald Welte08b11ab2022-02-10 18:56:41 +0100554 if as_json:
555 result = self._cmd.rs.read_record_dec(r)
556 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
557 else:
558 result = self._cmd.rs.read_record(r)
559 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
560
Harald Weltec91085e2022-02-10 18:05:45 +0100561 # When the select response does not return the number of records, read until we hit the
562 # first record that cannot be read.
563 else:
564 r = 1
565 while True:
566 try:
Harald Welte08b11ab2022-02-10 18:56:41 +0100567 if as_json:
568 result = self._cmd.rs.read_record_dec(r)
569 self._cmd.poutput("update_record_decoded %d '%s'" % (r, json.dumps(result[0], cls=JsonEncoder)))
570 else:
571 result = self._cmd.rs.read_record(r)
572 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Harald Weltec91085e2022-02-10 18:05:45 +0100573 except SwMatchError as e:
574 # We are past the last valid record - stop
575 if e.sw_actual == "9402":
576 break
577 # Some other problem occurred
578 else:
579 raise e
Harald Weltec91085e2022-02-10 18:05:45 +0100580 r = r + 1
581 elif structure == 'ber_tlv':
582 tags = self._cmd.rs.retrieve_tags()
583 for t in tags:
584 result = self._cmd.rs.retrieve_data(t)
585 (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
586 self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
587 else:
588 raise RuntimeError(
589 'Unsupported structure "%s" of file "%s"' % (structure, filename))
590 except Exception as e:
591 bad_file_str = '/'.join(df_path_list) + \
592 "/" + str(filename) + ", " + str(e)
593 self._cmd.poutput("# bad file: %s" % bad_file_str)
594 context['ERR'] += 1
595 context['BAD'].append(bad_file_str)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100596
Harald Weltec91085e2022-02-10 18:05:45 +0100597 # When reading the file is done, make sure the parent file is
598 # selected again. This will be the usual case, however we need
599 # to check before since we must not select the same DF twice
600 if df != self._cmd.rs.selected_file:
601 self._cmd.rs.select(df.fid or df.aid, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200602
Harald Weltec91085e2022-02-10 18:05:45 +0100603 self._cmd.poutput("#")
Philipp Maier24f7bd32021-02-25 17:06:18 +0100604
Harald Weltec91085e2022-02-10 18:05:45 +0100605 export_parser = argparse.ArgumentParser()
606 export_parser.add_argument(
607 '--filename', type=str, default=None, help='only export specific file')
Harald Welte08b11ab2022-02-10 18:56:41 +0100608 export_parser.add_argument(
609 '--json', action='store_true', help='export as JSON (less reliable)')
Philipp Maier24f7bd32021-02-25 17:06:18 +0100610
Harald Weltec91085e2022-02-10 18:05:45 +0100611 @cmd2.with_argparser(export_parser)
612 def do_export(self, opts):
613 """Export files to script that can be imported back later"""
614 context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
615 'DF_SKIP': 0, 'DF_SKIP_REASON': []}
616 if opts.filename:
Harald Welte08b11ab2022-02-10 18:56:41 +0100617 self.export(opts.filename, context, opts.json)
Harald Weltec91085e2022-02-10 18:05:45 +0100618 else:
Harald Welte08b11ab2022-02-10 18:56:41 +0100619 self.walk(0, self.export, context, opts.json)
Philipp Maier80ce71f2021-04-19 21:24:23 +0200620
Harald Weltec91085e2022-02-10 18:05:45 +0100621 self._cmd.poutput(boxed_heading_str("Export summary"))
Philipp Maier80ce71f2021-04-19 21:24:23 +0200622
Harald Weltec91085e2022-02-10 18:05:45 +0100623 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
624 self._cmd.poutput("# bad files: %u" % context['ERR'])
625 for b in context['BAD']:
626 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200627
Harald Weltec91085e2022-02-10 18:05:45 +0100628 self._cmd.poutput("# skipped dedicated files(s): %u" %
629 context['DF_SKIP'])
630 for b in context['DF_SKIP_REASON']:
631 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200632
Harald Weltec91085e2022-02-10 18:05:45 +0100633 if context['ERR'] and context['DF_SKIP']:
634 raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
635 context['ERR'], context['DF_SKIP']))
636 elif context['ERR']:
637 raise RuntimeError(
638 "unable to export %i elementary file(s)" % context['ERR'])
639 elif context['DF_SKIP']:
640 raise RuntimeError(
641 "unable to export %i dedicated files(s)" % context['ERR'])
Harald Welteb2edd142021-01-08 23:29:35 +0100642
Harald Weltec91085e2022-02-10 18:05:45 +0100643 def do_reset(self, opts):
644 """Reset the Card."""
645 atr = self._cmd.rs.reset(self._cmd)
646 self._cmd.poutput('Card ATR: %s' % atr)
647 self._cmd.update_prompt()
Harald Weltedaf2b392021-05-03 23:17:29 +0200648
Harald Weltec91085e2022-02-10 18:05:45 +0100649 def do_desc(self, opts):
650 """Display human readable file description for the currently selected file"""
651 desc = self._cmd.rs.selected_file.desc
652 if desc:
653 self._cmd.poutput(desc)
654 else:
655 self._cmd.poutput("no description available")
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200656
Harald Weltec91085e2022-02-10 18:05:45 +0100657 def do_verify_adm(self, arg):
658 """VERIFY the ADM1 PIN"""
659 if arg:
660 # use specified ADM-PIN
661 pin_adm = sanitize_pin_adm(arg)
662 else:
663 # try to find an ADM-PIN if none is specified
664 result = card_key_provider_get_field(
665 'ADM1', key='ICCID', value=self._cmd.iccid)
666 pin_adm = sanitize_pin_adm(result)
667 if pin_adm:
668 self._cmd.poutput(
669 "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
670 else:
671 raise ValueError(
672 "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
Philipp Maiera8c9ea92021-09-16 12:51:46 +0200673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 if pin_adm:
675 self._cmd.card.verify_adm(h2b(pin_adm))
676 else:
677 raise ValueError("error: cannot authenticate, no adm-pin!")
678
Harald Welteafb8d3f2022-02-11 16:03:06 +0100679 apdu_cmd_parser = argparse.ArgumentParser()
680 apdu_cmd_parser.add_argument('APDU', type=str, help='APDU as hex string')
681
682 @cmd2.with_argparser(apdu_cmd_parser)
683 def do_apdu(self, opts):
684 """Send a raw APDU to the card, and print SW + Response.
685 DANGEROUS: pySim-shell will not know any card state changes, and
686 not continue to work as expected if you e.g. select a different file."""
687 data, sw = self._cmd.card._scc._tp.send_apdu(opts.APDU)
688 self._cmd.poutput("SW: %s %s, RESP: %s" % (sw, self._cmd.rs.interpret_sw(sw), data))
689
Harald Welteb2edd142021-01-08 23:29:35 +0100690
Harald Welte31d2cf02021-04-03 10:47:29 +0200691@with_default_category('ISO7816 Commands')
692class Iso7816Commands(CommandSet):
Harald Weltec91085e2022-02-10 18:05:45 +0100693 def __init__(self):
694 super().__init__()
Harald Welte31d2cf02021-04-03 10:47:29 +0200695
Harald Weltec91085e2022-02-10 18:05:45 +0100696 def do_select(self, opts):
697 """SELECT a File (ADF/DF/EF)"""
698 if len(opts.arg_list) == 0:
699 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
700 path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
701 False)
702 self._cmd.poutput("currently selected file: " +
703 '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
704 return
Harald Welte31d2cf02021-04-03 10:47:29 +0200705
Harald Weltec91085e2022-02-10 18:05:45 +0100706 path = opts.arg_list[0]
707 fcp_dec = self._cmd.rs.select(path, self._cmd)
708 self._cmd.update_prompt()
709 self._cmd.poutput_json(fcp_dec)
Harald Welte31d2cf02021-04-03 10:47:29 +0200710
Harald Weltec91085e2022-02-10 18:05:45 +0100711 def complete_select(self, text, line, begidx, endidx) -> List[str]:
712 """Command Line tab completion for SELECT"""
713 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
714 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200715
Harald Weltec91085e2022-02-10 18:05:45 +0100716 def get_code(self, code):
717 """Use code either directly or try to get it from external data source"""
718 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
Harald Welte31d2cf02021-04-03 10:47:29 +0200719
Harald Weltec91085e2022-02-10 18:05:45 +0100720 if str(code).upper() not in auto:
721 return sanitize_pin_adm(code)
Harald Welte31d2cf02021-04-03 10:47:29 +0200722
Harald Weltec91085e2022-02-10 18:05:45 +0100723 result = card_key_provider_get_field(
724 str(code), key='ICCID', value=self._cmd.iccid)
725 result = sanitize_pin_adm(result)
726 if result:
727 self._cmd.poutput("found %s '%s' for ICCID '%s'" %
728 (code.upper(), result, self._cmd.iccid))
729 else:
730 self._cmd.poutput("cannot find %s for ICCID '%s'" %
731 (code.upper(), self._cmd.iccid))
732 return result
Harald Welte31d2cf02021-04-03 10:47:29 +0200733
Harald Weltec91085e2022-02-10 18:05:45 +0100734 verify_chv_parser = argparse.ArgumentParser()
735 verify_chv_parser.add_argument(
736 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
737 verify_chv_parser.add_argument(
738 '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 +0200739
Harald Weltec91085e2022-02-10 18:05:45 +0100740 @cmd2.with_argparser(verify_chv_parser)
741 def do_verify_chv(self, opts):
742 """Verify (authenticate) using specified PIN code"""
743 pin = self.get_code(opts.pin_code)
744 (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin))
745 self._cmd.poutput("CHV verification successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200746
Harald Weltec91085e2022-02-10 18:05:45 +0100747 unblock_chv_parser = argparse.ArgumentParser()
748 unblock_chv_parser.add_argument(
749 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
750 unblock_chv_parser.add_argument(
751 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
752 unblock_chv_parser.add_argument(
753 '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 +0200754
Harald Weltec91085e2022-02-10 18:05:45 +0100755 @cmd2.with_argparser(unblock_chv_parser)
756 def do_unblock_chv(self, opts):
757 """Unblock PIN code using specified PUK code"""
758 new_pin = self.get_code(opts.new_pin_code)
759 puk = self.get_code(opts.puk_code)
760 (data, sw) = self._cmd.card._scc.unblock_chv(
761 opts.pin_nr, h2b(puk), h2b(new_pin))
762 self._cmd.poutput("CHV unblock successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200763
Harald Weltec91085e2022-02-10 18:05:45 +0100764 change_chv_parser = argparse.ArgumentParser()
765 change_chv_parser.add_argument(
766 '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
767 change_chv_parser.add_argument(
768 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
769 change_chv_parser.add_argument(
770 '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 +0200771
Harald Weltec91085e2022-02-10 18:05:45 +0100772 @cmd2.with_argparser(change_chv_parser)
773 def do_change_chv(self, opts):
774 """Change PIN code to a new PIN code"""
775 new_pin = self.get_code(opts.new_pin_code)
776 pin = self.get_code(opts.pin_code)
777 (data, sw) = self._cmd.card._scc.change_chv(
778 opts.pin_nr, h2b(pin), h2b(new_pin))
779 self._cmd.poutput("CHV change successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200780
Harald Weltec91085e2022-02-10 18:05:45 +0100781 disable_chv_parser = argparse.ArgumentParser()
782 disable_chv_parser.add_argument(
783 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
784 disable_chv_parser.add_argument(
785 '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 +0200786
Harald Weltec91085e2022-02-10 18:05:45 +0100787 @cmd2.with_argparser(disable_chv_parser)
788 def do_disable_chv(self, opts):
789 """Disable PIN code using specified PIN code"""
790 pin = self.get_code(opts.pin_code)
791 (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin))
792 self._cmd.poutput("CHV disable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200793
Harald Weltec91085e2022-02-10 18:05:45 +0100794 enable_chv_parser = argparse.ArgumentParser()
795 enable_chv_parser.add_argument(
796 '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
797 enable_chv_parser.add_argument(
798 '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 +0200799
Harald Weltec91085e2022-02-10 18:05:45 +0100800 @cmd2.with_argparser(enable_chv_parser)
801 def do_enable_chv(self, opts):
802 """Enable PIN code using specified PIN code"""
803 pin = self.get_code(opts.pin_code)
804 (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin))
805 self._cmd.poutput("CHV enable successful")
Harald Welte31d2cf02021-04-03 10:47:29 +0200806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 def do_deactivate_file(self, opts):
Harald Welte799c3542022-02-15 15:56:28 +0100808 """Deactivate the currently selected EF"""
Harald Weltec91085e2022-02-10 18:05:45 +0100809 (data, sw) = self._cmd.card._scc.deactivate_file()
Harald Weltea4631612021-04-10 18:17:55 +0200810
Harald Welte799c3542022-02-15 15:56:28 +0100811 activate_file_parser = argparse.ArgumentParser()
812 activate_file_parser.add_argument('NAME', type=str, help='File name or FID of file to activate')
813 @cmd2.with_argparser(activate_file_parser)
Harald Weltec91085e2022-02-10 18:05:45 +0100814 def do_activate_file(self, opts):
815 """Activate the specified EF"""
Harald Welte799c3542022-02-15 15:56:28 +0100816 (data, sw) = self._cmd.rs.activate_file(opts.NAME)
Harald Welte485692b2021-05-25 22:21:44 +0200817
Harald Weltec91085e2022-02-10 18:05:45 +0100818 def complete_activate_file(self, text, line, begidx, endidx) -> List[str]:
819 """Command Line tab completion for ACTIVATE FILE"""
820 index_dict = {1: self._cmd.rs.selected_file.get_selectable_names()}
821 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
Harald Welte31d2cf02021-04-03 10:47:29 +0200822
Harald Weltec91085e2022-02-10 18:05:45 +0100823 open_chan_parser = argparse.ArgumentParser()
824 open_chan_parser.add_argument(
825 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200826
Harald Weltec91085e2022-02-10 18:05:45 +0100827 @cmd2.with_argparser(open_chan_parser)
828 def do_open_channel(self, opts):
829 """Open a logical channel."""
830 (data, sw) = self._cmd.card._scc.manage_channel(
831 mode='open', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200832
Harald Weltec91085e2022-02-10 18:05:45 +0100833 close_chan_parser = argparse.ArgumentParser()
834 close_chan_parser.add_argument(
835 'chan_nr', type=int, default=0, help='Channel Number')
Harald Welte703f9332021-04-10 18:39:32 +0200836
Harald Weltec91085e2022-02-10 18:05:45 +0100837 @cmd2.with_argparser(close_chan_parser)
838 def do_close_channel(self, opts):
839 """Close a logical channel."""
840 (data, sw) = self._cmd.card._scc.manage_channel(
841 mode='close', lchan_nr=opts.chan_nr)
Harald Welte703f9332021-04-10 18:39:32 +0200842
Harald Weltec91085e2022-02-10 18:05:45 +0100843 def do_status(self, opts):
844 """Perform the STATUS command."""
845 fcp_dec = self._cmd.rs.status()
846 self._cmd.poutput_json(fcp_dec)
Harald Welte34b05d32021-05-25 22:03:13 +0200847
Harald Weltec91085e2022-02-10 18:05:45 +0100848 suspend_uicc_parser = argparse.ArgumentParser()
849 suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60,
850 help='Proposed minimum duration of suspension')
851 suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60,
852 help='Proposed maximum duration of suspension')
Harald Welteec950532021-10-20 13:09:00 +0200853
Harald Weltec91085e2022-02-10 18:05:45 +0100854 # not ISO7816-4 but TS 102 221
855 @cmd2.with_argparser(suspend_uicc_parser)
856 def do_suspend_uicc(self, opts):
857 """Perform the SUSPEND UICC command. Only supported on some UICC."""
858 (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
859 max_len_secs=opts.max_duration_secs)
860 self._cmd.poutput(
861 'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
Harald Welteec950532021-10-20 13:09:00 +0200862
Harald Welte703f9332021-04-10 18:39:32 +0200863
Harald Weltef2e761c2021-04-11 11:56:44 +0200864option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
865 formatter_class=argparse.ArgumentDefaultsHelpFormatter)
Harald Welte28c24312021-04-11 12:19:36 +0200866argparse_add_reader_args(option_parser)
Harald Weltec8ff0262021-04-11 12:06:13 +0200867
868global_group = option_parser.add_argument_group('General Options')
869global_group.add_argument('--script', metavar='PATH', default=None,
Harald Welte28c24312021-04-11 12:19:36 +0200870 help='script with pySim-shell commands to be executed automatically at start-up')
Harald Weltec91085e2022-02-10 18:05:45 +0100871global_group.add_argument('--csv', metavar='FILE',
872 default=None, help='Read card data from CSV file')
Philipp Maier76667642021-09-22 16:53:22 +0200873global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
Harald Weltec91085e2022-02-10 18:05:45 +0100874 help="Use automatic card handling machine")
Harald Weltec8ff0262021-04-11 12:06:13 +0200875
876adm_group = global_group.add_mutually_exclusive_group()
877adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None,
878 help='ADM PIN used for provisioning (overwrites default)')
879adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_adm_hex', default=None,
880 help='ADM PIN used for provisioning, as hex string (16 characters long)')
Harald Welteb2edd142021-01-08 23:29:35 +0100881
882
883if __name__ == '__main__':
884
Harald Weltec91085e2022-02-10 18:05:45 +0100885 # Parse options
886 opts = option_parser.parse_args()
Harald Welteb2edd142021-01-08 23:29:35 +0100887
Harald Weltec91085e2022-02-10 18:05:45 +0100888 # If a script file is specified, be sure that it actually exists
889 if opts.script:
890 if not os.access(opts.script, os.R_OK):
891 print("Invalid script file!")
892 sys.exit(2)
Philipp Maier13e258d2021-04-08 17:48:49 +0200893
Harald Weltec91085e2022-02-10 18:05:45 +0100894 # Register csv-file as card data provider, either from specified CSV
895 # or from CSV file in home directory
896 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
897 if opts.csv:
898 card_key_provider_register(CardKeyProviderCsv(opts.csv))
899 if os.path.isfile(csv_default):
900 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +0100901
Harald Weltec91085e2022-02-10 18:05:45 +0100902 # Init card reader driver
903 sl = init_reader(opts)
904 if sl is None:
905 exit(1)
Philipp Maierea95c392021-09-16 13:10:19 +0200906
Harald Weltec91085e2022-02-10 18:05:45 +0100907 # Create command layer
908 scc = SimCardCommands(transport=sl)
Philipp Maierea95c392021-09-16 13:10:19 +0200909
Harald Weltec91085e2022-02-10 18:05:45 +0100910 # Create a card handler (for bulk provisioning)
911 if opts.card_handler_config:
912 ch = CardHandlerAuto(None, opts.card_handler_config)
913 else:
914 ch = CardHandler(sl)
Philipp Maier76667642021-09-22 16:53:22 +0200915
Harald Weltec91085e2022-02-10 18:05:45 +0100916 # Detect and initialize the card in the reader. This may fail when there
917 # is no card in the reader or the card is unresponsive. PysimApp is
918 # able to tolerate and recover from that.
919 try:
920 rs, card = init_card(sl)
921 app = PysimApp(card, rs, sl, ch, opts.script)
922 except:
923 print("Card initialization failed with an exception:")
924 print("---------------------8<---------------------")
925 traceback.print_exc()
926 print("---------------------8<---------------------")
927 print("(you may still try to recover from this manually by using the 'equip' command.)")
928 print(
929 " it should also be noted that some readers may behave strangely when no card")
930 print(" is inserted.)")
931 print("")
932 app = PysimApp(None, None, sl, ch, opts.script)
Philipp Maierea95c392021-09-16 13:10:19 +0200933
Harald Weltec91085e2022-02-10 18:05:45 +0100934 # If the user supplies an ADM PIN at via commandline args authenticate
935 # immediately so that the user does not have to use the shell commands
936 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
937 if pin_adm:
938 if not card:
939 print("Card error, cannot do ADM verification with supplied ADM pin now.")
940 try:
941 card.verify_adm(h2b(pin_adm))
942 except Exception as e:
943 print(e)
Philipp Maier228c98e2021-03-10 20:14:06 +0100944
Harald Weltec91085e2022-02-10 18:05:45 +0100945 app.cmdloop()