#!/usr/bin/python3 # # A swiss army knife tool for smart cards that conform to ISO7816-4, and in # particular GSM and USIMs. # # License # ------- # # Copyright (c) 2007,2014,2015,2016,2018,2020,2021 Russell Stuart. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or (at # your option) any later version. # # The copyright holders grant you an additional permission under Section 7 # of the GNU Affero General Public License, version 3, exempting you from # the requirement in Section 6 of the GNU General Public License, version 3, # to accompany Corresponding Source with Installation Information for the # Program or any work based on the Program. You are still required to # comply with all other Section 6 requirements to provide Corresponding # Source. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # import binascii import collections import iso7816 import pcsclite import os import string import sys APDU_CLASS = iso7816.CLS_GSM_SIM RESPONSE_NEEDED = ( iso7816.STATUS_SUCCESS_DATA[0], iso7816.STATUS_DATA_WAITING[0], ) SelectResult = collections.namedtuple( 'SelectResult', ('file_id', 'cmd', 'data', 'status')) ef_arr_path = {} ef_dir = None tracing = False # # Transmit a command to the card and get the response. # def transmit(card, cmd): data = card.transmit(cmd) if tracing: sys.stdout.write( '%s=%s' % (iso7816.b2a_hex(cmd), iso7816.b2a_hex(data),)) if data[-2] in RESPONSE_NEEDED: getcmd = ( iso7816.to_bytes((cmd[0], iso7816.INS_GSM_GET_RESPONSE,)) + b'\x00\x00' + data[1:2]) data = card.transmit(getcmd) if tracing: sys.stdout.write( ', %s=%s' % (iso7816.b2a_hex(getcmd), iso7816.b2a_hex(data),)) if tracing: sys.stdout.write('\n') response = data[:-2] status = data[-2:] return (response, data[-2:]) # # Print some trace to say what happened when a command was executed. # def trace(arg, data, status): trace = '%s = %s' % (arg, iso7816.b2a_hex(data)) if status[0] in RESPONSE_NEEDED: message = '' else: message = ': %s' % iso7816.status2desc(status) sys.stdout.write(trace + message + '\n') # # Select a file by Smart Card File ID. # def select_by_file_id(card, file_id): cmd = iso7816.to_bytes((APDU_CLASS, iso7816.INS_GSM_SELECT_FILE,)) if APDU_CLASS == iso7816.CLS_GSM_SIM: cmd += binascii.a2b_hex('000002%04x' % file_id) else: cmd += binascii.a2b_hex('000402%04x' % file_id) return SelectResult(file_id, cmd, *transmit(card, cmd)) # # Select a file by Smart Card Application ID. # def select_aid(card, aid): cmd = ( iso7816.to_bytes((APDU_CLASS, iso7816.INS_GSM_SELECT_FILE,)) + b'\x04') cmd += b'\x00' if APDU_CLASS == iso7816.CLS_GSM_SIM else b'\x04' cmd += iso7816.to_bytes((len(aid),)) + aid return SelectResult(aid, cmd, *transmit(card, cmd)) # # Read the file EFdir # def get_ef_dir(card): global ef_dir if ef_dir is None: global tracing old_tracing = tracing tracing = False try: ef_dir = read_file( card, os.path.join('//', '%04x' % (iso7816.FILE_ID_EF_DIR,))) finally: tracing = old_tracing return ef_dir # # Find an Applications AID given a prefix. # def aid_by_prefix(card, prefix): def n(rec): return iso7816.aid_prefix(rec[4:4 + iso7816.num8(rec[3])]) ef_dir = get_ef_dir(card) searchaid = iso7816.aid_prefix(binascii.a2b_hex(prefix)) records = [a for a in ef_dir[1:] if n(a).startswith(searchaid)] if not records: return None return records[0][4:4 + iso7816.num8(records[0][3])] # # Select a file given a full path. # def select_path(card, path): files = path.split('/') if files and files[0] == '': if files[1] == '': fid = iso7816.FILE_ID_MASTER select_result = select_by_file_id(card, fid) if files == ['', '', '']: files = ['', ''] else: aid = aid_by_prefix(card, files[1]) if not aid: aid = b'\x00' * 16 select_result = select_aid(card, aid) files = files[2:] for dir in files: file_id = int(dir, 16) select_result = select_by_file_id(card, file_id) if select_result.status != iso7816.STATUS_SUCCESS: break return select_result # # Read the contents of a file into a list, one item in the list per record, # indexed by record number. # def read_file(card, path): select_result = select_path(card, path) if select_result.status != iso7816.STATUS_SUCCESS: return None select = iso7816.Select(select_result.data) if select.file_type != iso7816.FILE_TYPE_EF: raise Exception('Can\'t read a DF!') if select.reclen == 0: cmd = ( iso7816.to_bytes((APDU_CLASS, iso7816.INS_GSM_READ_BINARY,)) + '\x00\x00' + iso7816.to_bytes((select.file_size,))) data, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: trace('read_file:%s' % path, data, status) return None return [data] result = [None] for recno in range(1, select.file_size // select.reclen + 1): cmd = iso7816.to_bytes(( APDU_CLASS, iso7816.INS_GSM_READ_RECORD, recno, iso7816.RECORD_P2_RECNO, select.reclen,)) data, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: trace('%s[%02x]' % (arg, recno), data, status) return None result.append(data) return result # # Get the appropriate in EFarr a given file. # def add_ef_arr(card, path, fileid): ef_arr_file = read_file( card, os.path.join(path, '%04x' % iso7816.num16(fileid))) if ef_arr_file is None: ef_arr_file = ef_arr_path.get( os.path.dirname(path), {}).get(fileid, []) ef_arr_path.setdefault(path, {})[fileid] = ef_arr_file # # Print the results of a select command. # def print_select(card, arg, path, select_result): if select_result.status != iso7816.STATUS_SUCCESS: trace(arg, select_result.data, select_result.status) else: dir, fid = os.path.split(path) re_do = True while re_do: re_do = False ef_arr = ef_arr_path.get(dir, {}) select = iso7816.Select(select_result.data, ef_arr=ef_arr) for ef_arr_fileid in select.ef_arr: if ef_arr_fileid not in ef_arr: re_do = True global tracing old_tracing = tracing tracing = False try: add_ef_arr(card, dir, ef_arr_fileid) if isinstance(select_result.file_id, int): select_by_file_id(card, select_result.file_id) else: select_aid(card, select_result.file_id) finally: tracing = old_tracing sys.stdout.write('%s = %s\n' % (arg, select,)) # # Output (print) a file. This function just reads the records and # passes each record to a "decode" function to do the actual printing. # def output_file(card, arg, path, select_result, decode): def p(endrec): if startrec is None: pass elif startrec == endrec: sys.stdout.write('%s[%d] = %s\n' % (arg, startrec, decode(rec))) else: sys.stdout.write( '%s[%d..%d] = %s\n' % (arg, startrec, endrec, decode(rec))) select = iso7816.Select(select_result.data) if select.file_type != iso7816.FILE_TYPE_EF: print_select(card, arg, path, select_result) return if select.reclen in (0, None): result = b'' while len(result) < select.file_size: len_in_bytes = min(select.file_size - len(result), 255) cmd = iso7816.to_bytes(( APDU_CLASS, iso7816.INS_GSM_READ_BINARY, len(result) >> 8, len(result) % 256, len_in_bytes)) data, status = transmit(card, cmd) result += data if status != iso7816.STATUS_SUCCESS: trace(arg, data, status) return sys.stdout.write('%s = %s\n' % (arg, decode(result))) else: rec = None startrec = None for recno in range(1, select.file_size // select.reclen + 1): cmd = iso7816.to_bytes(( APDU_CLASS, iso7816.INS_GSM_READ_RECORD, recno, iso7816.RECORD_P2_RECNO, select.reclen,)) data, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: p(recno - 1) trace('%s[%02x]' % (arg, recno), data, status) return if data != rec: p(recno - 1) startrec = recno rec = data p(recno) # # Dump a record. # def decode_dump(data): repeats = 0 while len(data) > repeats + 1 and data[-2 - repeats] == data[-1 - repeats]: repeats += 1 if repeats <= 2: return iso7816.b2a_hex(data) return '%s*%d' % (iso7816.b2a_hex(data[:-repeats]), repeats) # # Dump the contents of a file in hex. # def dump_file(card, arg, path, select_result): output_file(card, arg, path, select_result, decode_dump) # # Pretty print a file if we know its format, otherwise dump it in hex. # def print_file(card, arg, filepath, select_result): def decode(data): try: return ef.decode(data) except Exception: return 'decode %r failed, raw data=%s' % ( ef.format_spec, decode_dump(data),) ef = iso7816.EfGsm.find_path(filepath) if not ef: return dump_file(card, arg, filepath, select_result) return output_file(card, arg, filepath, select_result, decode) # # Run through the entire directory tree, passing each file in turn to the # function passed. # def tree(card, arg, function, roots=None): if roots: new_dirs = roots[:] else: new_dirs = ['//'] if APDU_CLASS == iso7816.CLS_GSM_USIM: new_dirs += [ '/' + iso7816.b2a_hex(r[4:4 + iso7816.num8(r[3])]) for r in get_ef_dir(card)[1:] ] dirs_done = {} for dir_path in new_dirs: select_result = select_path(card, dir_path) if select_result.status != iso7816.STATUS_SUCCESS: print_select(card, arg, dir_path, select_result) return dirs_done[dir_path] = select_result while new_dirs: dir_path = new_dirs[0] del new_dirs[0] dirname = dir_path if dir_path == '//' else dir_path + '/' cmdname = arg + ':' + dirname[:-1] function(card, cmdname, dir_path, dirs_done[dir_path]) sys.stdout.flush() cmdname = arg + ':' + dirname sys.stderr.write(cmdname) dir_selected = False sub_dirs = [] for file_id in range(0, 0x10000): # # Don't select the master file. # st = ( iso7816.FILE_ID_MASTER, iso7816.FILE_ID_CURRENT_DF, iso7816.FILE_ID_CURRENT_ADF,) if file_id in st: continue filename = '%04x' % file_id # # Select the correct directory if we moved. # if not dir_selected: select_result = select_path(card, dir_path) if select_result.status != iso7816.STATUS_SUCCESS: sys.stderr.write('\b \b' * len(cmdname)) sys.stderr.flush() print_select(card, cmdname, dir_path, select_result) return dir_selected = True sys.stderr.write(filename) sys.stderr.flush() select_result = select_by_file_id(card, file_id) sys.stderr.write('\b \b' * len(filename)) st = (iso7816.STATUS_FILE_NOT_FOUND, iso7816.STATUS_NOSUCH_FILE) if select_result.status in st: continue sys.stderr.write('\b \b' * len(cmdname)) sys.stderr.flush() filepath = os.path.join(dir_path, filename) if select_result.status != iso7816.STATUS_SUCCESS: print_select(card, cmdname, filepath, select_result) if select_result.status != iso7816.STATUS_FILE_INVALID: return sys.stdout.flush() sys.stderr.write(cmdname) continue select = iso7816.Select(select_result.data) select.id = file_id if select.file_type == iso7816.FILE_TYPE_EF: function(card, cmdname + filename, filepath, select_result) else: dir_selected = False # # Ignore if this is a DF we have seen before. It could be: # - The MF. # - A DF in our parents parent. # - Our parents parent. # ancestor = os.path.dirname(dir_path) ancestor_child = os.path.join(ancestor, filename) dup = ( os.path.basename(ancestor) == filename or ancestor_child in dirs_done) if dup: function(card, cmdname + filename, filepath, None) elif select.dfname: # The assumption is if you want to see a dfname you will # select it by aid, so we don't expand it. function(card, cmdname + filename, filepath, select_result) else: sub_dirs.append(filepath) dirs_done[filepath] = select_result sys.stdout.flush() sys.stderr.write(cmdname) new_dirs = sub_dirs + new_dirs sys.stderr.write('\b \b' * len(cmdname)) sys.stderr.flush() # # Do some operation with a pin. These all have a common format. # The params is a list of pin numbers, separated by commas. # def pin_op(card, arg, ins, pinnr, params): pin_field = ''.join( (pin + (b'\xff' * (8 - len(pin)))) if pin else b'' for pin in params.encode('latin1').split(b',')) if APDU_CLASS == iso7816.CLS_GSM_USIM and pinnr == 1: pinnr = 0x81 cmd = iso7816.to_bytes((APDU_CLASS, ins, 0, pinnr, len(pin_field),)) cmd += pin_field data, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: trace(arg, data, status) return cmd, data, status # # Send an activate/deactivate command. # def activate_op(card, arg, path, instruction): dir, file_name = os.path.split(path) if dir: select_result = select_path(card, dir) if select_result[2] != iso7816.STATUS_SUCCESS: print_select(card, arg, path, select_result) return file_name = os.path.basename(path) cmd = iso7816.to_bytes((APDU_CLASS, instruction, 0, 0, 2,)) cmd += file_name data, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: trace(arg, data, status) # # Execute the "activate" command. Sends a "ACTIVATE" command for the # following path. # def activate_cmd(card, arg, params): activate_op(card, arg, params, iso7816.INS_GSM_REHABILITATE) # # Execute the "class" command. Sets the APDU class for the following # command to be the parameter. # def class_cmd(card, arg, params): global APDU_CLASS APDU_CLASS = int(params) # # Execute the "deactivate" command. Sends a "DEACTIVATE" command for the # following path. # def deactivate_cmd(card, arg, params): activate_op(card, arg, params, iso7816.INS_GSM_INVALIDATE) # # Execute the "dir" command. List all files in the currently selected # directory. If a prefix is given only files with that prefix are listed. # def dir_cmd(card, arg, params): toks = params.split(',') path = toks[0] dir_selected = False for param in toks[1:]: begin = -1 if len(param) == 0: begin = 0 end = 0x10000 else: begin = int(param[:2], 16) * 256 end = begin + 0x100 for fileid in range(begin, end): if not dir_selected: select_result_dir = select_path(card, path) if select_result_dir.status != iso7816.STATUS_SUCCESS: print_select(card, arg, path, select_result_dir) return dir_selected = True filename = '%04x' % fileid sys.stdout.write(filename) sys.stdout.flush() select_result_ef = select_by_file_id(card, fileid) sys.stdout.write('\b \b' * 5) if select_result_ef.status in DIRCMD_ST_NOT_FOUND: continue dir_selected = False filepath = os.path.join(path, filename) print_select(card, arg, filepath, select_result_ef) if select_result_ef.status not in DIRCMD_ST_WORKED: return DIRCMD_ST_WORKED = (iso7816.STATUS_SUCCESS, iso7816.STATUS_FILE_INVALID) DIRCMD_ST_NOT_FOUND = ( iso7816.STATUS_FILE_NOT_FOUND, iso7816.STATUS_NOSUCH_FILE,) # # Execute the "dumptree" command. This pretty prints every file to stdout. # If params is given only the trees specificed are printed. If a files # format isn't known it is just dumped. # def dumptree_cmd(card, arg, params): def function(card, arg, path, select_result): if select_result: dump_file(card, arg, path, select_result) if not params: roots = None else: roots = params.split(',') tree(card, arg, function, roots) # # Execute the "dump" command. This dumps a file in hex to stdout. # def dump_cmd(card, arg, params): path = params select_result = select_path(card, params) if select_result.status != iso7816.STATUS_SUCCESS: print_select(card, arg, params, select_result) return dump_file(card, arg, path, select_result) # # Execute the "print" command. This pretty prints a file to stdout if its # format is known, otherwise just dump it in hex. # def print_cmd(card, arg, params): path = params select_result = select_path(card, params) if select_result.status != iso7816.STATUS_SUCCESS: print_select(card, arg, params, select_result) return print_file(card, arg, params, select_result) # # Execute the "printtree" command. This pretty prints the entire directory # tree to stdout. If params is given only the trees specificed are printed. # If a files format isn't known it is just dumped. # def printtree_cmd(card, arg, params): def function(card, arg, path, select_result): if select_result: print_file(card, arg, path, select_result) tree(card, arg, function, params.split(',') if params else None) # # Execute the "hex" command. It sends a raw APDU to the card and prints the # response. The APDU is given in hex. # def hex_cmd(card, arg, params): cmd = binascii.a2b_hex(arg.replace('.', '')) data, status = transmit(card, cmd) trace(arg, data, status) # # Execute the "help" command. It prints help. # def help_cmd(card, arg, params): def w(s): sys.stdout.write(' ' + s + '\n') w('?, h, help - Print this message.') w('HEX.APDU - Send hex digits optionally with . as a raw APDU. ') w('activate:P - Activate/Rehabilitate path P.') w('changeP:O,N - Change pin P from O to N.') w('deactivate:P - Deactivate/Invalidate path P.') w('dir:path,fid,... - Print all files in directory \'path\'.') w('disableP:N - Disable pin P. N is its current value.') w('enableP:N - Enable pin P setting it to N.') w('dump:path - Print a hex dump of EF path.') w('gsm - Use GSM class commands.') w('pinP:N - Enter N for a pin P.') w('print:path - Decode EF and print.') w('printtree - Find all EF\'s in the tree and print.') w('pukP:U,N - Unblock a pin P with puk U, setting it to N.') w('s:path - Select a file.') w('trace:T - Turn tracing of APDU\'s T=on or T=off.') w('tree - Print the file system tree.') w('update:P,R,D - Update record R in file P with data D.') w('usim - Use USIM class commands.') # # Execute the "s" command. The selects a file given its path. # def sel_cmd(card, arg, params): select_result = select_path(card, params) print_select(card, arg, params, select_result) # # Execute the "tree" command. Prints all files in the card's directory # tree. If params are supplied, then only the parts of the directory tree # given are printed. # def tree_cmd(card, arg, params): def function(card, cmd, path, select_result): if select_result is None: sys.stdout.write('%s = Seen\n' % cmd) else: print_select(card, cmd, path, select_result) tree(card, arg, function, params.split(',') if params else None) # # Execute the "change1" command. This changes pin1. # def change1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_CHANGE_CHV, 1, params) # # Execute the "change2" command. This changes pin2. # def change2_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_CHANGE_CHV, 2, params) # # Execute the "disable1" command. This disables pin1. # def disable1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_DISABLE_CHV, 1, params) # # Execute the "disable2" command. This disables pin2. # def disable2_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_DISABLE_CHV, 2, params) # # Execute the "enable1" command. This enables pin1. # def enable1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_ENABLE_CHV, 1, params) # # Execute the "enable2" command. This enables pin2. # def enable2_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_ENABLE_CHV, 2, params) # # Execute the "pin1" command. This proves to the SIM we know pin1, so # that commands that require it will execute. # def pin1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_VERIFY_CHV, 1, params) # # Execute the "pin2" command. This proves to the SIM we know pin2, so # that commands that require it will execute. # def pin2_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_VERIFY_CHV, 2, params) # # Execute the "pinA1" command. This proves to the SIM we know admin # pin 1, so that commands that require it will execute. # def pinA1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_VERIFY_CHV, 10, params) # # Execute the "pinA4" command. This proves to the SIM we know admin # pin 4, so that commands that require it will execute. # def pinA4_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_VERIFY_CHV, 13, params) # # Execute the "puk1" command. This unlocks pin1. # def puk1_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_UNBLOCK_CHV, 1, params) # # Execute the "puk2" command. This unlocks pin2. # def puk2_cmd(card, arg, params): pin_op(card, arg, iso7816.INS_GSM_UNBLOCK_CHV, 2, params) # # Execute the "trace" command. This turns tracing on or off. # def trace_cmd(card, arg, params): value = { '1': True, 'on': True, 't': True, 'true': True, 'yes': True, 'y': True, '0': False, 'off': False, 'f': False, 'false': False, 'no': False, 'n': False, }.get(params.lower()) if value is None: sys.stderr.write(arg + ' = eh?') else: global tracing tracing = value # # Execute the "gsm" command. This changes the APDU class to 0xa0, ie # the class used by the older GSM SIM's. # def gsm_cmd(card, arg, params): global APDU_CLASS APDU_CLASS = iso7816.CLS_GSM_SIM card.reconnect() # # Update a file with some data. # def update_cmd(card, arg, params): # # Parse the aguments. # toks = params.split(',', 2) if len(toks) != 3: sys.stderr.write( '%s: wrong number of arguments to update command\n' % (me,)) return filepath, rec, data = toks if not rec: recno = 0 else: try: recno = int(rec) except ValueError as e: sys.stderr.write( '%s: record %s isn\'t a number.\n' % (me, recno,)) return toks = data.split('*') if len(toks) == 1: hexdata = toks[0] elif len(toks) != 2: sys.stderr.write('%s: bad format for data.\n' % (me, recno,)) return else: hexdata, repeat = toks try: repeat_count = int(repeat) except ValueError as e: sys.stderr.write( '%s: repeat count %s isn\'t a number.\n' % (me, repeat,)) return hexdata = hexdata + hexdata[-2:] * repeat_count try: bindata = binascii.a2b_hex(hexdata) except TypeError as e: sys.stderr.write( '%s: data isn\'t a hex string, %s.\n' % (me, str(e),)) return # # Arguments appear valid. Select the file. # select_result = select_path(card, filepath) if select_result.status != iso7816.STATUS_SUCCESS: print_select(card, arg, filepath, select_result) return # # Different commands depending on whether we are updating a record # of the entire file. # if recno != 0: cmd = iso7816.to_bytes(( APDU_CLASS, iso7816.INS_GSM_UPDATE_RECORD, recno, iso7816.RECORD_P2_RECNO, len(bindata),)) else: cmd = iso7816.to_bytes(( APDU_CLASS, iso7816.INS_GSM_UPDATE_BINARY, 0, 0, len(bindata),)) cmd += bindata response, status = transmit(card, cmd) if status != iso7816.STATUS_SUCCESS: trace(arg, response, status) # # Execute the "usim" command. This changes the APDU class to 0x00, ie # the class used by USIM's. Doing this also changes the format of a # few commands and allows file selection by AID. # def usim_cmd(card, arg, params): global APDU_CLASS APDU_CLASS = iso7816.CLS_GSM_USIM card.reconnect() # # Commands that can be supplied on the command line. # commands = { '--help': help_cmd, '-h': help_cmd, '-?': help_cmd, '?': help_cmd, 'activate': activate_cmd, 'change1': change1_cmd, 'change2': change2_cmd, 'class': class_cmd, 'deactivate': deactivate_cmd, 'dir': dir_cmd, 'disable1': disable1_cmd, 'disable2': disable2_cmd, 'dump': dump_cmd, 'dumptree': dumptree_cmd, 'enable1': enable1_cmd, 'enable2': enable2_cmd, 'h': help_cmd, 'help': help_cmd, 'gsm': gsm_cmd, 'pin1': pin1_cmd, 'pin2': pin2_cmd, 'pinA1': pinA1_cmd, 'pinA4': pinA4_cmd, 'print': print_cmd, 'printtree': printtree_cmd, 'puk1': puk1_cmd, 'puk2': puk2_cmd, 's': sel_cmd, 'trace': trace_cmd, 'tree': tree_cmd, 'update': update_cmd, 'usim': usim_cmd, } # # Entry point. # def main(argv=sys.argv): if len(argv) == 1: sys.stdout.write('usage: ' + argv[0] + ' command...\n') sys.stdout.write('commands:\n') help_cmd(None, None, None) return # # Create a connection to the smartcard. # context = pcsclite.Context() try: readers = context.list_readers() if len(readers) == 0: sys.stderr.write(argv[0] + ': no smart card reader found.\n') sys.exit(1) sys.stderr.write(argv[0] + ': using reader ' + readers[0] + '\n') card = context.connect(readers[0]) try: # # Process the commands. # card.begin_transaction() card.reconnect() for arg in argv[1:]: toks = arg.split(':', 1) params = toks[1] if len(toks) == 2 else '' if toks[0] in commands: commands[toks[0]](card, arg, params) elif ( len(toks) == 1 and toks[0].strip(string.hexdigits + '.') == '' ): hex_cmd(card, arg, None) else: sys.stderr.write( '%s: unrecognised command %r\n' % (argv[0], arg,)) sys.exit(1) card.end_transaction() finally: del card finally: del context if __name__ == "__main__" and '__python_rc_debugger__' not in sys.modules: main(sys.argv)