blob: 1d81eb8bd13f2193770340736138e3ec5df8633e [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
23
24import cmd2
25from cmd2 import style, fg, bg
26from cmd2 import CommandSet, with_default_category, with_argparser
27import argparse
28
29import os
30import sys
31from optparse import OptionParser
Philipp Maier2b11c322021-03-17 12:37:39 +010032from pathlib import Path
Harald Welteb2edd142021-01-08 23:29:35 +010033
34from pySim.ts_51_011 import EF, DF, EF_SST_map, EF_AD_mode_map
35from pySim.ts_31_102 import EF_UST_map, EF_USIM_ADF_map
36from pySim.ts_31_103 import EF_IST_map, EF_ISIM_ADF_map
37
38from pySim.exceptions import *
39from pySim.commands import SimCardCommands
Harald Welte7829d8a2021-04-10 11:28:53 +020040from pySim.transport import init_reader, ApduTracer
Harald Welteb2edd142021-01-08 23:29:35 +010041from pySim.cards import card_detect, Card
Harald Welte5e749a72021-04-10 17:18:17 +020042from pySim.utils import h2b, swap_nibbles, rpad, h2s, JsonEncoder
Harald Welte6e0458d2021-04-03 11:52:37 +020043from pySim.utils import dec_st, sanitize_pin_adm, tabulate_str_list, is_hex
Harald Welteb2edd142021-01-08 23:29:35 +010044from pySim.card_handler import card_handler
45
Philipp Maierff9dae22021-02-25 17:03:21 +010046from pySim.filesystem import CardMF, RuntimeState, CardDF, CardADF
Harald Welteb2edd142021-01-08 23:29:35 +010047from pySim.ts_51_011 import CardProfileSIM, DF_TELECOM, DF_GSM
48from pySim.ts_102_221 import CardProfileUICC
Harald Welte5ce35242021-04-02 20:27:05 +020049from pySim.ts_31_102 import CardApplicationUSIM
50from pySim.ts_31_103 import CardApplicationISIM
Harald Welteb2edd142021-01-08 23:29:35 +010051
Harald Welte4442b3d2021-04-03 09:00:16 +020052from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
Philipp Maier2b11c322021-03-17 12:37:39 +010053
54
Harald Welteb2edd142021-01-08 23:29:35 +010055class PysimApp(cmd2.Cmd):
56 CUSTOM_CATEGORY = 'pySim Commands'
Philipp Maier681bc7b2021-03-10 19:52:41 +010057 def __init__(self, card, rs, script = None):
Harald Welte31d2cf02021-04-03 10:47:29 +020058 basic_commands = [Iso7816Commands(), PySimCommands()]
Harald Welteb2edd142021-01-08 23:29:35 +010059 super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
Philipp Maier681bc7b2021-03-10 19:52:41 +010060 use_ipython=True, auto_load_commands=False, command_sets=basic_commands, startup_script=script)
Harald Welteb2edd142021-01-08 23:29:35 +010061 self.intro = style('Welcome to pySim-shell!', fg=fg.red)
62 self.default_category = 'pySim-shell built-in commands'
63 self.card = card
Philipp Maier2b11c322021-03-17 12:37:39 +010064 iccid, sw = self.card.read_iccid()
65 self.iccid = iccid
Harald Welteb2edd142021-01-08 23:29:35 +010066 self.rs = rs
67 self.py_locals = { 'card': self.card, 'rs' : self.rs }
Harald Welteb2edd142021-01-08 23:29:35 +010068 self.numeric_path = False
69 self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names',
70 onchange_cb=self._onchange_numeric_path))
Philipp Maier38c74f62021-03-17 17:19:52 +010071 self.conserve_write = True
72 self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
73 onchange_cb=self._onchange_conserve_write))
Harald Welteb2edd142021-01-08 23:29:35 +010074 self.update_prompt()
Harald Welte1748b932021-04-06 21:12:25 +020075 self.json_pretty_print = True
76 self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output'))
Harald Welte7829d8a2021-04-10 11:28:53 +020077 self.apdu_trace = False
78 self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
79 onchange_cb=self._onchange_apdu_trace))
Harald Welte1748b932021-04-06 21:12:25 +020080
81 def poutput_json(self, data, force_no_pretty = False):
82 """line cmd2.putput() but for a json serializable dict."""
83 if force_no_pretty or self.json_pretty_print == False:
Harald Welte5e749a72021-04-10 17:18:17 +020084 output = json.dumps(data, cls=JsonEncoder)
Harald Welte1748b932021-04-06 21:12:25 +020085 else:
Harald Welte5e749a72021-04-10 17:18:17 +020086 output = json.dumps(data, cls=JsonEncoder, indent=4)
Harald Welte1748b932021-04-06 21:12:25 +020087 self.poutput(output)
Harald Welteb2edd142021-01-08 23:29:35 +010088
89 def _onchange_numeric_path(self, param_name, old, new):
90 self.update_prompt()
91
Philipp Maier38c74f62021-03-17 17:19:52 +010092 def _onchange_conserve_write(self, param_name, old, new):
93 self.rs.conserve_write = new
94
Harald Welte7829d8a2021-04-10 11:28:53 +020095 def _onchange_apdu_trace(self, param_name, old, new):
96 if new == True:
97 self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self)
98 else:
99 self.card._scc._tp.apdu_tracer = None
100
101 class Cmd2ApduTracer(ApduTracer):
102 def __init__(self, cmd2_app):
103 self.cmd2 = app
104
105 def trace_response(self, cmd, sw, resp):
106 self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:]))
107 self.cmd2.poutput("<- %s: %s" % (sw, resp))
108
Harald Welteb2edd142021-01-08 23:29:35 +0100109 def update_prompt(self):
110 path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path)
111 self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
112
113 @cmd2.with_category(CUSTOM_CATEGORY)
114 def do_intro(self, _):
115 """Display the intro banner"""
116 self.poutput(self.intro)
117
118 @cmd2.with_category(CUSTOM_CATEGORY)
119 def do_verify_adm(self, arg):
120 """VERIFY the ADM1 PIN"""
Philipp Maier2b11c322021-03-17 12:37:39 +0100121 if arg:
122 # use specified ADM-PIN
123 pin_adm = sanitize_pin_adm(arg)
124 else:
125 # try to find an ADM-PIN if none is specified
Harald Welte4442b3d2021-04-03 09:00:16 +0200126 result = card_key_provider_get_field('ADM1', key='ICCID', value=self.iccid)
Philipp Maier2b11c322021-03-17 12:37:39 +0100127 pin_adm = sanitize_pin_adm(result)
128 if pin_adm:
Philipp Maierb63766b2021-03-26 11:50:21 +0100129 self.poutput("found ADM-PIN '%s' for ICCID '%s'" % (result, self.iccid))
130 else:
Philipp Maierdc4402e2021-04-07 14:16:13 +0200131 self.poutput("cannot find ADM-PIN for ICCID '%s'" % (self.iccid))
Philipp Maierb63766b2021-03-26 11:50:21 +0100132 return
Philipp Maier2b11c322021-03-17 12:37:39 +0100133
134 if pin_adm:
135 self.card.verify_adm(h2b(pin_adm))
136 else:
137 self.poutput("error: cannot authenticate, no adm-pin!")
Harald Welteb2edd142021-01-08 23:29:35 +0100138
Philipp Maier2558aa62021-03-10 16:20:02 +0100139 @cmd2.with_category(CUSTOM_CATEGORY)
140 def do_desc(self, opts):
141 """Display human readable file description for the currently selected file"""
142 desc = self.rs.selected_file.desc
143 if desc:
144 self.poutput(desc)
145 else:
146 self.poutput("no description available")
Harald Welteb2edd142021-01-08 23:29:35 +0100147
Harald Welte31d2cf02021-04-03 10:47:29 +0200148@with_default_category('pySim Commands')
149class PySimCommands(CommandSet):
Harald Welteb2edd142021-01-08 23:29:35 +0100150 def __init__(self):
151 super().__init__()
152
Philipp Maier5d3e2592021-02-22 17:22:16 +0100153 dir_parser = argparse.ArgumentParser()
154 dir_parser.add_argument('--fids', help='Show file identifiers', action='store_true')
155 dir_parser.add_argument('--names', help='Show file names', action='store_true')
156 dir_parser.add_argument('--apps', help='Show applications', action='store_true')
157 dir_parser.add_argument('--all', help='Show all selectable identifiers and names', action='store_true')
158
159 @cmd2.with_argparser(dir_parser)
160 def do_dir(self, opts):
161 """Show a listing of files available in currently selected DF or MF"""
162 if opts.all:
163 flags = []
164 elif opts.fids or opts.names or opts.apps:
165 flags = ['PARENT', 'SELF']
166 if opts.fids:
167 flags += ['FIDS', 'AIDS']
168 if opts.names:
169 flags += ['FNAMES', 'ANAMES']
170 if opts.apps:
171 flags += ['ANAMES', 'AIDS']
172 else:
173 flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
174 selectables = list(self._cmd.rs.selected_file.get_selectable_names(flags = flags))
175 directory_str = tabulate_str_list(selectables, width = 79, hspace = 2, lspace = 1, align_left = True)
176 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
177 self._cmd.poutput('/'.join(path_list))
178 path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
179 self._cmd.poutput('/'.join(path_list))
180 self._cmd.poutput(directory_str)
181 self._cmd.poutput("%d files" % len(selectables))
Harald Welteb2edd142021-01-08 23:29:35 +0100182
Philipp Maierff9dae22021-02-25 17:03:21 +0100183 def walk(self, indent = 0, action = None, context = None):
184 """Recursively walk through the file system, starting at the currently selected DF"""
185 files = self._cmd.rs.selected_file.get_selectables(flags = ['FNAMES', 'ANAMES'])
186 for f in files:
187 if not action:
188 output_str = " " * indent + str(f) + (" " * 250)
189 output_str = output_str[0:25]
190 if isinstance(files[f], CardADF):
191 output_str += " " + str(files[f].aid)
192 else:
193 output_str += " " + str(files[f].fid)
194 output_str += " " + str(files[f].desc)
195 self._cmd.poutput(output_str)
Philipp Maierf408a402021-04-09 21:16:12 +0200196
Philipp Maierff9dae22021-02-25 17:03:21 +0100197 if isinstance(files[f], CardDF):
Philipp Maierf408a402021-04-09 21:16:12 +0200198 skip_df=False
199 try:
200 fcp_dec = self._cmd.rs.select(f, self._cmd)
201 except Exception as e:
202 skip_df=True
203 df = self._cmd.rs.selected_file
204 df_path_list = df.fully_qualified_path(True)
205 df_skip_reason_str = '/'.join(df_path_list) + "/" + str(f) + ", " + str(e)
206 if context:
207 context['DF_SKIP'] += 1
208 context['DF_SKIP_REASON'].append(df_skip_reason_str)
209
210 # If the DF was skipped, we never have entered the directory
211 # below, so we must not move up.
212 if skip_df == False:
213 self.walk(indent + 1, action, context)
214 fcp_dec = self._cmd.rs.select("..", self._cmd)
215
Philipp Maierff9dae22021-02-25 17:03:21 +0100216 elif action:
Philipp Maierb152a9e2021-04-01 17:13:03 +0200217 df_before_action = self._cmd.rs.selected_file
Philipp Maierff9dae22021-02-25 17:03:21 +0100218 action(f, context)
Philipp Maierb152a9e2021-04-01 17:13:03 +0200219 # When walking through the file system tree the action must not
220 # always restore the currently selected file to the file that
221 # was selected before executing the action() callback.
222 if df_before_action != self._cmd.rs.selected_file:
223 raise RuntimeError("inconsistant walk, %s is currently selected but expecting %s to be selected"
224 % (str(self._cmd.rs.selected_file), str(df_before_action)))
Philipp Maierff9dae22021-02-25 17:03:21 +0100225
226 def do_tree(self, opts):
227 """Display a filesystem-tree with all selectable files"""
228 self.walk()
229
Philipp Maier24f7bd32021-02-25 17:06:18 +0100230 def export(self, filename, context):
Philipp Maierac34dcc2021-04-01 17:19:05 +0200231 """ Select and export a single file """
Philipp Maier24f7bd32021-02-25 17:06:18 +0100232 context['COUNT'] += 1
Philipp Maierac34dcc2021-04-01 17:19:05 +0200233 df = self._cmd.rs.selected_file
234
235 if not isinstance(df, CardDF):
236 raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df))
237
238 df_path_list = df.fully_qualified_path(True)
239 df_path_list_fid = df.fully_qualified_path(False)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100240
241 self._cmd.poutput("#" * 80)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200242 file_str = '/'.join(df_path_list) + "/" + str(filename) + " " * 80
Philipp Maier24f7bd32021-02-25 17:06:18 +0100243 self._cmd.poutput("# " + file_str[0:77] + "#")
244 self._cmd.poutput("#" * 80)
245
Philipp Maierac34dcc2021-04-01 17:19:05 +0200246 self._cmd.poutput("# directory: %s (%s)" % ('/'.join(df_path_list), '/'.join(df_path_list_fid)))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100247 try:
248 fcp_dec = self._cmd.rs.select(filename, self._cmd)
Philipp Maierac34dcc2021-04-01 17:19:05 +0200249 self._cmd.poutput("# file: %s (%s)" % (self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100250
251 fd = fcp_dec['file_descriptor']
252 structure = fd['structure']
253 self._cmd.poutput("# structure: %s" % str(structure))
254
Philipp Maierac34dcc2021-04-01 17:19:05 +0200255 for f in df_path_list:
Philipp Maier24f7bd32021-02-25 17:06:18 +0100256 self._cmd.poutput("select " + str(f))
Philipp Maierac34dcc2021-04-01 17:19:05 +0200257 self._cmd.poutput("select " + self._cmd.rs.selected_file.name)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100258
259 if structure == 'transparent':
260 result = self._cmd.rs.read_binary()
261 self._cmd.poutput("update_binary " + str(result[0]))
262 if structure == 'cyclic' or structure == 'linear_fixed':
263 num_of_rec = fd['num_of_rec']
264 for r in range(1, num_of_rec + 1):
265 result = self._cmd.rs.read_record(r)
266 self._cmd.poutput("update_record %d %s" % (r, str(result[0])))
Philipp Maier24f7bd32021-02-25 17:06:18 +0100267 except Exception as e:
Philipp Maierac34dcc2021-04-01 17:19:05 +0200268 bad_file_str = '/'.join(df_path_list) + "/" + str(filename) + ", " + str(e)
Philipp Maier24f7bd32021-02-25 17:06:18 +0100269 self._cmd.poutput("# bad file: %s" % bad_file_str)
270 context['ERR'] += 1
271 context['BAD'].append(bad_file_str)
272
Philipp Maierac34dcc2021-04-01 17:19:05 +0200273 # When reading the file is done, make sure the parent file is
274 # selected again. This will be the usual case, however we need
275 # to check before since we must not select the same DF twice
276 if df != self._cmd.rs.selected_file:
277 self._cmd.rs.select(df.fid or df.aid, self._cmd)
278
Philipp Maier24f7bd32021-02-25 17:06:18 +0100279 self._cmd.poutput("#")
280
281 export_parser = argparse.ArgumentParser()
282 export_parser.add_argument('--filename', type=str, default=None, help='only export specific file')
283
284 @cmd2.with_argparser(export_parser)
285 def do_export(self, opts):
286 """Export files to script that can be imported back later"""
Philipp Maierf408a402021-04-09 21:16:12 +0200287 context = {'ERR':0, 'COUNT':0, 'BAD':[], 'DF_SKIP':0, 'DF_SKIP_REASON':[]}
Philipp Maier24f7bd32021-02-25 17:06:18 +0100288 if opts.filename:
289 self.export(opts.filename, context)
290 else:
291 self.walk(0, self.export, context)
292 self._cmd.poutput("# total files visited: %u" % context['COUNT'])
293 self._cmd.poutput("# bad files: %u" % context['ERR'])
294 for b in context['BAD']:
295 self._cmd.poutput("# " + b)
Philipp Maierf408a402021-04-09 21:16:12 +0200296
297 self._cmd.poutput("# skipped dedicated files(s): %u" % context['DF_SKIP'])
298 for b in context['DF_SKIP_REASON']:
299 self._cmd.poutput("# " + b)
300
301 if context['ERR'] and context['DF_SKIP']:
302 raise RuntimeError("unable to export %i elementry file(s) and %i dedicated file(s)" % (context['ERR'], context['DF_SKIP']))
303 elif context['ERR']:
304 raise RuntimeError("unable to export %i elementry file(s)" % context['ERR'])
305 elif context['DF_SKIP']:
306 raise RuntimeError("unable to export %i dedicated files(s)" % context['ERR'])
Harald Welteb2edd142021-01-08 23:29:35 +0100307
308
Harald Welte31d2cf02021-04-03 10:47:29 +0200309@with_default_category('ISO7816 Commands')
310class Iso7816Commands(CommandSet):
311 def __init__(self):
312 super().__init__()
313
314 def do_select(self, opts):
315 """SELECT a File (ADF/DF/EF)"""
316 if len(opts.arg_list) == 0:
317 path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
318 path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(False)
319 self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
320 return
321
322 path = opts.arg_list[0]
323 fcp_dec = self._cmd.rs.select(path, self._cmd)
324 self._cmd.update_prompt()
325 self._cmd.poutput(json.dumps(fcp_dec, indent=4))
326
327 def complete_select(self, text, line, begidx, endidx) -> List[str]:
328 """Command Line tab completion for SELECT"""
329 index_dict = { 1: self._cmd.rs.selected_file.get_selectable_names() }
330 return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
331
332 def get_code(self, code):
333 """Use code either directly or try to get it from external data source"""
334 auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2')
335
336 if str(code).upper() not in auto:
337 return sanitize_pin_adm(code)
338
339 result = card_key_provider_get_field(str(code), key='ICCID', value=self._cmd.iccid)
340 result = sanitize_pin_adm(result)
341 if result:
342 self._cmd.poutput("found %s '%s' for ICCID '%s'" % (code.upper(), result, self._cmd.iccid))
343 else:
344 self._cmd.poutput("cannot find %s for ICCID '%s'" % (code.upper(), self._cmd.iccid))
345 return result
346
347 verify_chv_parser = argparse.ArgumentParser()
348 verify_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
349 verify_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
350
351 @cmd2.with_argparser(verify_chv_parser)
352 def do_verify_chv(self, opts):
353 """Verify (authenticate) using specified PIN code"""
354 pin = self.get_code(opts.pin_code)
355 (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin))
356 self._cmd.poutput("CHV verfication successful")
357
358 unblock_chv_parser = argparse.ArgumentParser()
359 unblock_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
360 unblock_chv_parser.add_argument('puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
361 unblock_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
362
363 @cmd2.with_argparser(unblock_chv_parser)
364 def do_unblock_chv(self, opts):
365 """Unblock PIN code using specified PUK code"""
366 new_pin = self.get_code(opts.new_pin_code)
367 puk = self.get_code(opts.puk_code)
368 (data, sw) = self._cmd.card._scc.unblock_chv(opts.pin_nr, h2b(puk), h2b(new_pin))
369 self._cmd.poutput("CHV unblock successful")
370
371 change_chv_parser = argparse.ArgumentParser()
372 change_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
373 change_chv_parser.add_argument('pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
374 change_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
375
376 @cmd2.with_argparser(change_chv_parser)
377 def do_change_chv(self, opts):
378 """Change PIN code to a new PIN code"""
379 new_pin = self.get_code(opts.new_pin_code)
380 pin = self.get_code(opts.pin_code)
381 (data, sw) = self._cmd.card._scc.change_chv(opts.pin_nr, h2b(pin), h2b(new_pin))
382 self._cmd.poutput("CHV change successful")
383
384 disable_chv_parser = argparse.ArgumentParser()
385 disable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
386 disable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
387
388 @cmd2.with_argparser(disable_chv_parser)
389 def do_disable_chv(self, opts):
390 """Disable PIN code using specified PIN code"""
391 pin = self.get_code(opts.pin_code)
392 (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin))
393 self._cmd.poutput("CHV disable successful")
394
395 enable_chv_parser = argparse.ArgumentParser()
396 enable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
397 enable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
398
399 @cmd2.with_argparser(enable_chv_parser)
400 def do_enable_chv(self, opts):
401 """Enable PIN code using specified PIN code"""
402 pin = self.get_code(opts.pin_code)
403 (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin))
404 self._cmd.poutput("CHV enable successful")
405
406
Harald Welteb2edd142021-01-08 23:29:35 +0100407def parse_options():
408
409 parser = OptionParser(usage="usage: %prog [options]")
410
411 parser.add_option("-d", "--device", dest="device", metavar="DEV",
412 help="Serial Device for SIM access [default: %default]",
413 default="/dev/ttyUSB0",
414 )
415 parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
416 help="Baudrate used for SIM access [default: %default]",
417 default=9600,
418 )
419 parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
420 help="Which PC/SC reader number for SIM access",
421 default=None,
422 )
423 parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
424 help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
425 default=None,
426 )
427 parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
428 help="Baudrate used for modem's port [default: %default]",
429 default=115200,
430 )
431 parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
432 help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
433 default=None,
434 )
Philipp Maier681bc7b2021-03-10 19:52:41 +0100435 parser.add_option("--script", dest="script", metavar="PATH",
436 help="script with shell commands to be executed automatically",
437 default=None,
438 )
Harald Welteb2edd142021-01-08 23:29:35 +0100439
Philipp Maier2b11c322021-03-17 12:37:39 +0100440 parser.add_option("--csv", dest="csv", metavar="FILE",
441 help="Read card data from CSV file",
442 default=None,
443 )
444
Harald Welteb2edd142021-01-08 23:29:35 +0100445 parser.add_option("-a", "--pin-adm", dest="pin_adm",
446 help="ADM PIN used for provisioning (overwrites default)",
447 )
448 parser.add_option("-A", "--pin-adm-hex", dest="pin_adm_hex",
449 help="ADM PIN used for provisioning, as hex string (16 characters long",
450 )
451
452 (options, args) = parser.parse_args()
453
454 if args:
455 parser.error("Extraneous arguments")
456
457 return options
458
459
460
461if __name__ == '__main__':
462
463 # Parse options
464 opts = parse_options()
465
466 # Init card reader driver
467 sl = init_reader(opts)
468 if (sl == None):
469 exit(1)
470
471 # Create command layer
472 scc = SimCardCommands(transport=sl)
473
474 sl.wait_for_card();
475
476 card_handler = card_handler(sl)
477
478 card = card_detect("auto", scc)
479 if card is None:
480 print("No card detected!")
481 sys.exit(2)
482
483 profile = CardProfileUICC()
Harald Welte5ce35242021-04-02 20:27:05 +0200484 profile.add_application(CardApplicationUSIM)
485 profile.add_application(CardApplicationISIM)
Philipp Maier1e896f32021-03-10 17:02:53 +0100486
Harald Welteb2edd142021-01-08 23:29:35 +0100487 rs = RuntimeState(card, profile)
Harald Welte4f2c5462021-04-03 11:48:22 +0200488 # inform the transport that we can do context-specific SW interpretation
489 sl.set_sw_interpreter(rs)
Harald Welteb2edd142021-01-08 23:29:35 +0100490
491 # FIXME: do this dynamically
492 rs.mf.add_file(DF_TELECOM())
493 rs.mf.add_file(DF_GSM())
Harald Welteb2edd142021-01-08 23:29:35 +0100494
Philipp Maier13e258d2021-04-08 17:48:49 +0200495 # If a script file is specified, be sure that it actually exists
496 if opts.script:
497 if not os.access(opts.script, os.R_OK):
498 print("Invalid script file!")
499 sys.exit(2)
500
Philipp Maier681bc7b2021-03-10 19:52:41 +0100501 app = PysimApp(card, rs, opts.script)
Philipp Maier9c1a4ec2021-03-10 12:38:15 +0100502 rs.select('MF', app)
Philipp Maier228c98e2021-03-10 20:14:06 +0100503
Philipp Maier2b11c322021-03-17 12:37:39 +0100504 # Register csv-file as card data provider, either from specified CSV
505 # or from CSV file in home directory
506 csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv"
507 if opts.csv:
Harald Welte4442b3d2021-04-03 09:00:16 +0200508 card_key_provider_register(CardKeyProviderCsv(opts.csv))
Philipp Maier2b11c322021-03-17 12:37:39 +0100509 if os.path.isfile(csv_default):
Harald Welte4442b3d2021-04-03 09:00:16 +0200510 card_key_provider_register(CardKeyProviderCsv(csv_default))
Philipp Maier2b11c322021-03-17 12:37:39 +0100511
Philipp Maier228c98e2021-03-10 20:14:06 +0100512 # If the user supplies an ADM PIN at via commandline args authenticate
513 # immediatley so that the user does not have to use the shell commands
514 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
515 if pin_adm:
516 try:
517 card.verify_adm(h2b(pin_adm))
518 except Exception as e:
519 print(e)
520
Harald Welteb2edd142021-01-08 23:29:35 +0100521 app.cmdloop()