├── COPYING ├── README ├── TLV_utils.py ├── brutefid.py ├── cards ├── __init__.py ├── acos6sam_card.py ├── basic_card.py ├── building_blocks.py ├── cardos_card.py ├── cyberflex_card.py ├── generic_application.py ├── generic_card.py ├── gsm_card.py ├── iso_7816_4_card.py ├── iso_card.py ├── java_card.py ├── mtcos_card.py ├── nfc_application.py ├── passport_application.py ├── pn532_card.py ├── postcard_card.py ├── rfid_card.py ├── seccos_card.py ├── starcos_card.py ├── tcos_card.py └── vrs_application.py ├── crypto_utils.py ├── cyberflex-shell.e3p ├── cyberflex-shell.py ├── fingerpass.py ├── fingerprints.txt ├── gui ├── PassportGUI.py ├── __init__.py ├── ireadyou.py ├── ireadyou │ ├── ireadyou.glade │ └── ireadyou.gladep └── passport │ ├── passport.glade │ └── passport.gladep ├── oids.txt ├── parse-usbsnoop.py ├── pycsc-0.0.3_new-pcsc.patch ├── readers.py ├── readpass.py ├── shell.py ├── tests ├── __init__.py ├── __main__.py └── utilstest.py ├── tlvdecoder.py └── utils.py /README: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henryk/cyberflex-shell/0f7584aab47400ea346c938a3f13b7a099c22f2c/README -------------------------------------------------------------------------------- /brutefid.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | import utils, cards, TLV_utils, sys, binascii, time, traceback, smartcard, readers 5 | 6 | OPTIONS = "m:x:dD" 7 | LONG_OPTIONS = ["min-fid", "max-fid", "with-dirs", "dump-contents"] 8 | 9 | STATUS_INTERVAL = 10 10 | SPINNER = ['/','-','\\','|'] 11 | 12 | results_dir = {} 13 | results_file = {} 14 | contents_file = {} 15 | top_level = None 16 | start_time = time.time() 17 | loop = 0 18 | 19 | min_fid = 0 20 | max_fid = 0xffff 21 | with_dirs = False 22 | dump_contents = False 23 | 24 | def dump(data): 25 | print "Dump following (%i bytes)" % (len(data)) 26 | print utils.hexdump(data) 27 | try: 28 | print "Trying TLV parse:" 29 | print TLV_utils.decode(data, tags=card.TLV_OBJECTS, context = card.DEFAULT_CONTEXT) 30 | print "TLV parsed successfully" 31 | except (SystemExit, KeyboardInterrupt): 32 | raise 33 | except: 34 | print "TLV error" 35 | pass 36 | 37 | if __name__ == "__main__": 38 | c = readers.CommandLineArgumentHelper() 39 | 40 | (options, arguments) = c.getopt(sys.argv[1:], OPTIONS, LONG_OPTIONS) 41 | for option, value in options: 42 | if option in ("-m","--min-fid"): 43 | min_fid = int(value, 16) 44 | elif option in ("-x","--max_fid"): 45 | max_fid = int(value, 16) 46 | elif option in ("-d","--with-dirs"): 47 | with_dirs = not with_dirs 48 | elif option in ("-D", "--dump-contents"): 49 | dump_contents = not dump_contents 50 | 51 | if len(arguments) > 0: 52 | top_level = ("".join( ["".join(e.split()) for e in arguments] )).split("/") 53 | top_level = [binascii.unhexlify(e) for e in top_level] 54 | 55 | print "Reading /%s from %04X to %04X%s" % ( 56 | top_level is not None and "/".join("%r" % e for e in top_level) or "", 57 | min_fid, 58 | max_fid, 59 | with_dirs and " (DFs treated separately)" or "", 60 | ) 61 | 62 | card_object = c.connect() 63 | card = cards.new_card_object(card_object) 64 | cards.generic_card.DEBUG = False 65 | 66 | print "Using %s" % card.DRIVER_NAME 67 | 68 | card.change_dir() 69 | if top_level is not None: 70 | for e in top_level: 71 | if len(e) == 2: 72 | card.change_dir(e) 73 | else: 74 | card.select_application(e) 75 | 76 | root_node = cards.iso_7816_4_card.iso_node(generic_description="Brute Force Results Tree") 77 | 78 | #objective = (0x2f00, 0x5015) ## Test cases on an OpenSC formatted PKCS#15 card 79 | #objective = range(0xffff+1) 80 | #objective = range(0x3fff+1) + range(0x7000,0x7fff+1) + range(0xc000,0xd4ff+1) + range(0xd600+1,0xd7ff+1) + range(0xdc00+1,0xffff+1) 81 | objective = range(min_fid, max_fid+1) 82 | try: 83 | for fid in objective: 84 | data = chr(fid >> 8) + chr(fid & 0xff) 85 | if loop % STATUS_INTERVAL == 0: 86 | elapsed = time.time() - start_time 87 | status = "(elapsed: %i:%02i:%02i" % (elapsed / 3600, (elapsed / 60) % 60, elapsed % 60) 88 | try: 89 | eta = (elapsed / loop) * (len(objective) - loop) 90 | status = status + ", left: %i:%02i:%02i" % (eta / 3600, (eta / 60) % 60, eta % 60) 91 | except: pass 92 | if with_dirs: status = status + ", dirs: %2i" % len(results_dir) 93 | status = status + ", files: %2i)" % len(results_file) 94 | loop = loop + 1 95 | 96 | if with_dirs: 97 | try: 98 | result = card.change_dir(data) 99 | except smartcard.Exceptions.CardConnectionException: 100 | time.sleep(1) 101 | result = card.change_dir(data) 102 | if card.check_sw(result.sw): 103 | results_dir[fid] = result 104 | card.change_dir() 105 | if top_level is not None: 106 | for e in top_level: 107 | if len(e) == 2: 108 | card.change_dir(e) 109 | else: 110 | card.select_application(e) 111 | 112 | print >>sys.stderr, "\rDir %04X -> %02X%02X %s " % (fid, result.sw1, result.sw2, status), 113 | 114 | try: 115 | result = card.open_file(data) 116 | except smartcard.Exceptions.CardConnectionException: 117 | time.sleep(1) 118 | result = card.open_file(data) 119 | if card.check_sw(result.sw): 120 | results_file[fid] = result 121 | 122 | if dump_contents: 123 | contents, sw = card.read_binary_file() 124 | contents_result = [sw] 125 | if sw == '\x69\x81' or sw == '\x69\xf0': # Command incompatible with file structure, retry read_record 126 | # FIXME this logic for reading records is not correct 127 | print >>sys.stderr, "\rFile %04X -> %02X%02X %s Reading records... " % (fid, result.sw1, result.sw2, status), 128 | records = {} 129 | for i in range(256): 130 | if i%STATUS_INTERVAL == 0: 131 | print >>sys.stderr, "\rFile %04X -> %02X%02X %s Reading records... %s" % (fid, result.sw1, result.sw2, status, 132 | SPINNER[ (i/STATUS_INTERVAL) % len(SPINNER) ], 133 | ), 134 | records[i] = card.read_record(i, 4, 0) 135 | contents_result.append(records) 136 | elif sw == '\x69\x82': # Security status not satisfied 137 | pass 138 | elif sw == '\x90\x00': # Command execution successful 139 | contents_result.append(contents) 140 | elif len(contents) > 0: # Something was returned, assume successful execution 141 | contents_result.append(contents) 142 | 143 | contents_file[fid] = contents_result 144 | 145 | print >>sys.stderr, "\rFile %04X -> %02X%02X %s " % (fid, result.sw1, result.sw2, status), 146 | except (SystemExit, KeyboardInterrupt): 147 | raise 148 | except: 149 | traceback.print_exc() 150 | 151 | 152 | print >>sys.stderr 153 | 154 | print "="*80 155 | print "Results:" 156 | for fid, result in sorted(results_dir.items()): 157 | if results_file.has_key(fid): 158 | continue 159 | 160 | print "-"*80 161 | print "Dir\t%04X" % fid 162 | if len(result.data) > 0: 163 | print utils.hexdump(result.data) 164 | try: print TLV_utils.decode(result.data,tags=card.TLV_OBJECTS) 165 | except: print "Exception during TLV parse" 166 | 167 | for fid, result in sorted(results_file.items()): 168 | print "-"*80 169 | print "File\t%04X" % fid 170 | if len(result.data) > 0: 171 | print utils.hexdump(result.data) 172 | try: print TLV_utils.decode(result.data,tags=card.TLV_OBJECTS) 173 | except: print "Exception during TLV parse" 174 | 175 | if contents_file.has_key( fid ): 176 | contents_result = contents_file[fid] 177 | if contents_result[0] == '\x69\x81': 178 | print "Record-oriented file" 179 | elif contents_result[0] == '\x69\x82': 180 | print "Can't read file" 181 | elif len(contents_result) > 1: 182 | if contents_result[0] == '\x90\x00': 183 | print "Transparent file" 184 | else: 185 | print "Strange file (%02X%02X)" % (ord(contents_result[0][0]), ord(contents_result[0][1])) 186 | 187 | if len(contents_result) > 1: 188 | if isinstance(contents_result[1], str): 189 | dump(contents_result[1]) 190 | else: 191 | for index, data in contents_result[1].items(): 192 | if len(data) > 0: 193 | print "Record %i:" % index 194 | dump(data) 195 | print 196 | 197 | print 198 | 199 | print "<"*40 + ">"*40 200 | root_node.print_node() 201 | 202 | -------------------------------------------------------------------------------- /cards/__init__.py: -------------------------------------------------------------------------------- 1 | """This package contains different card-specific modules. 2 | 3 | The __init__.py file will automatically load all modules in the cards directory 4 | and import all classes that have a DRIVER_NAME attribute. If you want to write 5 | your own classes you should therefore make sure it has a DRIVER_NAME and then 6 | put it in the same directory as the other classes. Preferably you should derive 7 | from the generic_card.Card class.""" 8 | 9 | from sys import modules as _modules 10 | from dircache import listdir as _listdir 11 | from new import classobj as _classobj 12 | import inspect as _inspect 13 | 14 | for filename in _listdir(_modules[__name__].__path__[0]): 15 | if filename[-3:].lower() == ".py": 16 | possible_module = filename[:-3] 17 | if possible_module.lower() == "__init__": 18 | continue 19 | try: 20 | module = __import__(possible_module, globals(), locals(), []) 21 | for possible_class in dir(module): 22 | if hasattr(getattr(module, possible_class), "DRIVER_NAME"): 23 | setattr(_modules[__name__], possible_class, getattr(module, possible_class)) 24 | except ImportError: 25 | pass 26 | 27 | 28 | del filename, possible_module, module, possible_class 29 | 30 | def new_card_object(card): 31 | """Return a new object that will incorporate all classes that 32 | think that they can handle the given card. The object will always 33 | contain the Card class.""" 34 | 35 | card_classes = [Card] 36 | 37 | for card_class in dir(_modules[__name__]): 38 | card_class = getattr(_modules[__name__], card_class) 39 | 40 | if card_class in card_classes: 41 | continue 42 | 43 | if hasattr(card_class, "can_handle"): 44 | if card_class.can_handle(card): 45 | card_classes.append(card_class) 46 | 47 | return Cardmultiplexer( tuple(card_classes), card ) 48 | 49 | class Cardmultiplexer: 50 | """This class will provide an object that 'multiplexes' several card classes 51 | into one. 52 | 53 | This is somewhat different from multiple inheritance in that classes can 54 | be dynamically added and removed from instances of this class. It also 55 | provides support for merging some list and dictionary class attributes 56 | of the participating classes instead of overriding them.""" 57 | 58 | MERGE_DICTS = ("APPLICATIONS", "COMMANDS", "STATUS_WORDS", "VENDORS") 59 | MERGE_DICTS_RECURSIVE = ("TLV_OBJECTS", "STATUS_MAP") 60 | MERGE_LISTS = ("DRIVER_NAME", ) 61 | 62 | def __init__(self, classes, *args, **kwargs): 63 | """Creates a new Cardmultiplexer object. 64 | 65 | The first positional argument must be a list of classes that you want 66 | to initially melt together. 67 | 68 | Any additional positional or keyword arguments that you give will be saved 69 | and provided to the __init__ methods of all classes that you add. 70 | (You may change the saved arguments at a later time with 71 | set_init_arguments().)""" 72 | 73 | self._classes = [] 74 | self._classes_needed = [] 75 | self._init_args = args 76 | self._init_kwargs = kwargs 77 | 78 | self.add_classes(classes) 79 | 80 | 81 | def add_classes(self, classes): 82 | """Add some new classes to this Cardmultiplexer object. classes 83 | should be a sequence of class objects. 84 | 85 | Note that there are two tricky parts in this process: 86 | 1. We wouldn't want to add superclasses of classes that 87 | already are here. This usually makes no sense, because 88 | Python can handle the method resolution order in this 89 | case for itself pretty fine. 90 | 2. The way to get bound methods is kind of hackish: 91 | As soon as the list of classes changes we create a new 92 | class object incorporating all classes from _classes as 93 | well as this class (Cardmultiplexer) and then set our 94 | __class__ attribute to this new object.""" 95 | 96 | (newcls, delcls) = self._update_classes(list(classes), []) 97 | for cls in newcls: 98 | if hasattr(cls, "__init__"): 99 | cls.__init__(self, *self._init_args, **self._init_kwargs) 100 | 101 | self._merge_attributes() 102 | 103 | def remove_classes(self, classes): 104 | """Remove classes from this Cardmultiplexer object.""" 105 | (newcls, delcls) = self._update_classes([], list(classes)) 106 | 107 | self._merge_attributes() 108 | 109 | def _update_classes(self, addclasses, delclasses): 110 | """This handles the task of figuring out which classes to actually 111 | melt together. It uses two lists: new_classes and classes_needed: 112 | new_classes contains all the classes the user wishes to melt. Then 113 | classes will be continually added from new_classes to classes_needed 114 | unless there is already a subclass in this list. If a class is added 115 | then all superclasses of it will be removed from the list.""" 116 | new_classes = self._classes + addclasses 117 | for cls in delclasses: 118 | new_classes.remove(cls) 119 | 120 | classes_needed = [] 121 | 122 | for new_class in new_classes: 123 | already_covered = False 124 | 125 | ## Check if this class is already covered by classes_needed 126 | for potential_sub in classes_needed: 127 | if issubclass(potential_sub, new_class): 128 | already_covered = True 129 | break 130 | 131 | if not already_covered: 132 | ## Remove all super classes of this class and then add the class itself 133 | classes_needed = [cls for cls in classes_needed 134 | if not issubclass(new_class, cls)] 135 | classes_needed.append(new_class) 136 | 137 | diffplus = [cls for cls in classes_needed if cls not in self._classes_needed] 138 | diffminus = [cls for cls in self._classes_needed if cls not in classes_needed] 139 | 140 | self._classes = new_classes 141 | self._classes_needed = classes_needed 142 | namespace = {} 143 | for cls in classes_needed: 144 | namespace.update( cls.__dict__ ) 145 | self.__class__ = _classobj("Cardmultiplexer (merged)", 146 | tuple(classes_needed + [Cardmultiplexer]), namespace) 147 | return (diffplus,diffminus) 148 | 149 | def _merge_attributes(self): 150 | """Update the local copy of merged attributes.""" 151 | 152 | ordered_classes = list(self._classes) 153 | ordered_classes.sort(cmp=lambda a,b: (a!=b and ( issubclass(a,b) and 1 or -1 ) or 0)) 154 | 155 | for attr in self.MERGE_DICTS + self.MERGE_DICTS_RECURSIVE: 156 | tmpdict = {} 157 | have_one = False 158 | for cls in ordered_classes: 159 | if hasattr(cls, attr): 160 | if not attr in self.MERGE_DICTS_RECURSIVE: 161 | tmpdict.update( getattr(cls, attr) ) 162 | else: 163 | def recurse(target, source): 164 | for (key, value) in source.items(): 165 | if target.has_key(key): 166 | if isinstance(value, dict) and isinstance(target[key], dict): 167 | recurse( target[key], value ) 168 | elif isinstance(value, dict): 169 | target[key] = dict(value) 170 | elif isinstance(value, (tuple,list)) and isinstance(target[key], list): 171 | target[key].extend( [e for e in value if not e in target[key]] ) 172 | elif isinstance(value, (tuple,list)) and isinstance(target[key], tuple): 173 | target[key] = tuple( list(target[key]) + [e for e in value if not e in target[key]] ) 174 | else: 175 | target[key] = value 176 | else: 177 | if isinstance(value, dict): 178 | target[key] = dict(value) 179 | else: 180 | target[key] = value 181 | recurse( tmpdict, getattr(cls, attr) ) 182 | have_one = True 183 | if have_one: 184 | setattr(self, attr, tmpdict) 185 | 186 | for attr in self.MERGE_LISTS: 187 | tmplist = [] 188 | have_one = False 189 | for cls in self._classes: 190 | if hasattr(cls, attr): 191 | tmplist.extend( getattr(cls, attr) ) 192 | have_one = True 193 | if have_one: 194 | setattr(self, attr, tmplist) 195 | 196 | for cls in ordered_classes: 197 | if hasattr(cls, "post_merge"): 198 | cls.post_merge(self) 199 | -------------------------------------------------------------------------------- /cards/acos6sam_card.py: -------------------------------------------------------------------------------- 1 | import utils, binascii 2 | from iso_7816_4_card import * 3 | 4 | class ACOS6SAM_Card(ISO_7816_4_Card): 5 | DRIVER_NAME = ["ACOS6-SAM"] 6 | SELECT_FILE_P1 = 0x00 7 | 8 | ATRS = [ 9 | ("3bbe9600004103000000000000000000029000", None), 10 | ] 11 | -------------------------------------------------------------------------------- /cards/basic_card.py: -------------------------------------------------------------------------------- 1 | from iso_7816_4_card import * 2 | from generic_card import Card 3 | 4 | class BasicCard_Card(ISO_7816_4_Card): 5 | DRIVER_NAME = ["Basic Card"] 6 | 7 | ATRS = [ 8 | ("3BBC1800813120755A43.*", None), 9 | ] 10 | 11 | STATUS_MAP = dict(ISO_7816_4_Card.STATUS_MAP) 12 | STATUS_MAP[Card.PURPOSE_GET_RESPONSE] = () 13 | 14 | STATUS_WORDS = dict(ISO_7816_4_Card.STATUS_WORDS) 15 | STATUS_WORDS.update( { 16 | '61??': "Command should have been called with Le equal to %(SW2)i (0x%(SW2)02x)", 17 | } ) 18 | 19 | def post_merge(self): 20 | self.STATUS_MAP[Card.PURPOSE_GET_RESPONSE] = () 21 | -------------------------------------------------------------------------------- /cards/building_blocks.py: -------------------------------------------------------------------------------- 1 | "This module holds some commonly used card functionality that is not ISO" 2 | 3 | from utils import C_APDU, R_APDU 4 | import utils, TLV_utils 5 | 6 | class Card_with_ls: 7 | def _str_to_long(value): 8 | num = 0 9 | for i in value: 10 | num = num * 256 11 | num = num + ord(i) 12 | return num 13 | _str_to_long = staticmethod(_str_to_long) 14 | 15 | def _find_recursive(search_tag, data): 16 | while len(data) > 0: 17 | if ord(data[0]) in (0x00, 0xFF): 18 | data = data[1:] 19 | continue 20 | 21 | ber_class, constructed, tag, length, value, data = TLV_utils.tlv_unpack(data) 22 | if not constructed: 23 | if tag == search_tag: 24 | return value 25 | else: 26 | ret = Card_with_ls._find_recursive(search_tag, value) 27 | if ret is not None: return ret 28 | return None 29 | _find_recursive = staticmethod(_find_recursive) 30 | 31 | _ls_l_template = "%(name)-12s\t%(type)3s\t%(size)4s" 32 | def cmd_list(self, *options): 33 | """List all EFs and DFs in current DF. Call with -l for verbose information (caution: deselects current file)""" 34 | dirs = self.list_x(self.LIST_X_DF) 35 | files = self.list_x(self.LIST_X_EF) 36 | 37 | if "-l" in options: 38 | response_DF = {} 39 | response_EF = {} 40 | for DF in dirs: 41 | response_DF[DF] = self.select_file(0x01, self.SELECT_P2, DF) 42 | self.select_file(0x03, 0x00, "") 43 | for EF in files: 44 | response_EF[EF] = self.select_file(0x02, self.SELECT_P2, EF) 45 | 46 | self.sw_changed = False 47 | self.last_delta = None 48 | 49 | if "-l" in options: 50 | print self._ls_l_template % {"name": "Name", "type": "Type", "size": "Size"} 51 | dirs.sort() 52 | files.sort() 53 | for FID in dirs: 54 | name = "[" + utils.hexdump(FID, short=True) + "]" 55 | type = "DF" 56 | size = "" 57 | print self._ls_l_template % locals() 58 | for FID in files: 59 | name = " " + utils.hexdump(FID, short=True) + " " 60 | type = "EF" 61 | v = self._find_recursive(self.LS_L_SIZE_TAG, response_EF[FID].data) 62 | if v: 63 | size = self._str_to_long(v) 64 | else: 65 | size = "n/a" 66 | print self._ls_l_template % locals() 67 | else: 68 | print "\n".join( ["[%s]" % utils.hexdump(a, short=True) for a in dirs] 69 | + [" %s " % utils.hexdump(a, short=True) for a in files] ) 70 | 71 | class Card_with_80_aa(Card_with_ls): 72 | APDU_LIST_X = C_APDU("\x80\xaa\x01\x00\x00") 73 | LIST_X_DF = 1 74 | LIST_X_EF = 2 75 | LS_L_SIZE_TAG = 0x81 76 | 77 | def list_x(self, x): 78 | "Get a list of x objects, where x is one of 1 (DFs) or 2 (EFs) or 3 (DFs and EFs)" 79 | result = self.send_apdu(C_APDU(self.APDU_LIST_X, p1=x)) 80 | 81 | tail = result.data 82 | result_list = [] 83 | while len(tail) > 0: 84 | head, tail = tail[:2], tail[2:] 85 | result_list.append(head) 86 | return result_list 87 | 88 | def cmd_listdirs(self): 89 | "List DFs in current DF" 90 | result = self.list_x(1) 91 | print "DFs: " + ", ".join([utils.hexdump(a, short=True) for a in result]) 92 | 93 | def cmd_listfiles(self): 94 | "List EFs in current DF" 95 | result = self.list_x(2) 96 | print "EFs: " + ", ".join([utils.hexdump(a, short=True) for a in result]) 97 | 98 | class Card_with_read_binary: 99 | DATA_UNIT_SIZE=1 100 | HEXDUMP_LINELEN=16 101 | 102 | def read_binary_file(self, offset = 0): 103 | """Read from the currently selected EF. 104 | Repeat calls to READ BINARY as necessary to get the whole EF.""" 105 | 106 | if offset >= 1<<15: 107 | raise ValueError, "offset is limited to 15 bits" 108 | contents = "" 109 | had_one = False 110 | 111 | self.last_size = -1 112 | while True: 113 | command = C_APDU(self.APDU_READ_BINARY, p1 = offset >> 8, p2 = (offset & 0xff)) 114 | result = self.send_apdu(command) 115 | if len(result.data) > 0: 116 | contents = contents + result.data 117 | offset = offset + (len(result.data) / self.DATA_UNIT_SIZE) 118 | 119 | if self.last_size == len(contents): 120 | break 121 | else: 122 | self.last_size = len(contents) 123 | 124 | if not self.check_sw(result.sw): 125 | break 126 | else: 127 | had_one = True 128 | 129 | if had_one: ## If there was at least one successful pass, ignore any error SW. It probably only means "end of file" 130 | self.sw_changed = False 131 | self.last_delta = None 132 | 133 | return contents, result.sw 134 | 135 | def cmd_cat(self): 136 | "Print a hexdump of the currently selected file (e.g. consecutive READ BINARY)" 137 | contents, sw = self.read_binary_file() 138 | self.last_result = R_APDU(contents + self.last_sw) 139 | print utils.hexdump(contents,linelen=self.HEXDUMP_LINELEN) 140 | 141 | COMMANDS = { 142 | "cat": cmd_cat, 143 | } 144 | 145 | -------------------------------------------------------------------------------- /cards/cardos_card.py: -------------------------------------------------------------------------------- 1 | import utils, TLV_utils 2 | from iso_7816_4_card import * 3 | import building_blocks 4 | 5 | class CardOS_Card(ISO_7816_4_Card,building_blocks.Card_with_ls): 6 | DRIVER_NAME = ["CardOS"] 7 | 8 | ATRS = [ 9 | ("3bf2180002c10a31fe58c80874", None), 10 | ] 11 | 12 | APDU_LIFECYCLE = C_APDU("\x00\xCA\x01\x83\x00") 13 | APDU_PHASE_CONTROL = C_APDU("\x80\x10\x00\x00\x00") 14 | APDU_LIST_X = C_APDU("\x80\x16\x01\x00\x00") 15 | LIST_X_DF = 0 16 | LIST_X_EF = 1 17 | LS_L_SIZE_TAG = 0x80 18 | 19 | CARDOS_LIFE_CYCLE_STATUS_BYTE_DESCRIPTIONS = [ 20 | (0x10, "operational"), 21 | (0x20, "Administration"), 22 | (0x23, "Personalization"), 23 | (0x26, "Initialisation"), 24 | (0x34, "Manufacturing"), 25 | (0x3F, "Death"), 26 | (0x29, "Erase in Progress"), 27 | ] 28 | 29 | STATUS_WORDS = ( { 30 | "6283": "File is deactivated", 31 | "6300": "Authentication failed", 32 | "6581": "EEPROM error, command aborted", 33 | "6700": "LC invalid", 34 | "6881": "Logical channel not supported", 35 | "6981": "Command can not be used for file structure", 36 | "6982": "Required access right not granted", 37 | "6983": "BS object blocked", 38 | "6984": "BS object has invalid format", 39 | "6985": "No random number available", 40 | "6986": "No current EF selected", 41 | "6987": "Key object for SM not found", 42 | "6988": "Key object used for SM has invalid format", 43 | "6A80": "Invalid parameters in data field", 44 | "6A81": "Function/mode not supported", 45 | "6A82": "File not found", 46 | "6A83": "Record/object not found", 47 | "6A84": "Not enough memory in file / in file system available", 48 | "6A85": "LC does not fit the TLV structure of the data field", 49 | "6A86": "P1/P2 invalid", 50 | "6A87": "LC does not fit P1/P2", 51 | "6A88": "Object not found (GET DATA)", 52 | "6C00": "LC does not fit the data to be sent (e.g. SM)", 53 | "6D00": "INS invalid", 54 | "6E00": "CLA invalid (Hi nibble)", 55 | "6F00": "Technical error:\n + It was tried to create more than 254 records in a file\n + Package uses SDK version which is not compatible to API version\n + Package contains invalid statements (LOAD EXECUTABLE)", 56 | "6F81": "File is invalidated because of checksum error (prop.)", 57 | "6F82": "Not enough memory available in XRAM", 58 | "6F83": "Transaction error (i.e. command must not be used in transaction)", 59 | "6F84": "General protection fault (prop.)", 60 | "6F85": "Internal failure of PK-API (e.g. wrong CCMS format)", 61 | "6F86": "Key Object not found", 62 | "6F87": "Chaining error", 63 | "6FFF": "Internal assertion (invalid internal error)\n + This error is no runtime error, but an internal error which can occur because of a programming error only.", 64 | "9000": "Command executed correctly", 65 | "9001": "Command exectued correctly; EEPROM weakness detected (EEPROM written with second trial; the EEPROM area overwritten has a limited lifetime only)", 66 | "9850": "Overflow using INCREASE / underflow using DECREASE" 67 | } ) 68 | 69 | def list_x(self, x): 70 | "Get a list of x objects, where x is one of 0 (DFs) or 1 (EFs) or 2 (DFs and EFs)" 71 | ## FIXME I just guessed this information 72 | result = self.send_apdu(C_APDU(self.APDU_LIST_X, p1=x)) 73 | 74 | files = [] 75 | unpacked = TLV_utils.unpack(result.data) 76 | for tag, length, value in unpacked: 77 | if isinstance(value, list): 78 | for tag, length, value in value: 79 | if tag == 0x86: 80 | files.append(value) 81 | else: 82 | if tag == 0x86: 83 | files.append(value) 84 | 85 | return files 86 | 87 | def cmd_listdirs(self): 88 | "List DFs in current DF" 89 | result = self.list_x(0) 90 | print "DFs: " + ", ".join([utils.hexdump(a, short=True) for a in result]) 91 | 92 | def cmd_listfiles(self): 93 | "List EFs in current DF" 94 | result = self.list_x(1) 95 | print "EFs: " + ", ".join([utils.hexdump(a, short=True) for a in result]) 96 | 97 | def cmd_lifecycle(self): 98 | "Check the current lifecycle" 99 | result = self.send_apdu(C_APDU(self.APDU_LIFECYCLE)) 100 | #status = binascii.b2a_hex(result.data) 101 | for hex, mes in self.CARDOS_LIFE_CYCLE_STATUS_BYTE_DESCRIPTIONS: 102 | if (int(binascii.b2a_hex(result.data), 16) == hex): 103 | print "Satus: " + mes 104 | break 105 | 106 | def cmd_phase_control(self): 107 | "change lifecycle between Administration and Operational" 108 | result = self.send_apdu(C_APDU(self.APDU_PHASE_CONTROL)) 109 | 110 | 111 | COMMANDS = { 112 | "list_dirs": cmd_listdirs, 113 | "list_files": cmd_listfiles, 114 | "ls": building_blocks.Card_with_ls.cmd_list, 115 | "check_lifecycle": cmd_lifecycle, 116 | "phase_control": cmd_phase_control, 117 | } 118 | -------------------------------------------------------------------------------- /cards/cyberflex_card.py: -------------------------------------------------------------------------------- 1 | import utils, crypto_utils, binascii 2 | from java_card import * 3 | 4 | KEY_AUTH = 0x01 5 | KEY_MAC = 0x02 6 | KEY_KEK = 0x03 7 | DEFAULT_KEYSET = { 8 | KEY_AUTH: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F", 9 | KEY_MAC: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F", 10 | KEY_KEK: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F"} 11 | DEFAULT_CARD_MANAGER_AID = "\xA0\x00\x00\x00\x03\x00\x00" 12 | SECURE_CHANNEL_NONE = -1 13 | SECURE_CHANNEL_CLEAR = 0 14 | SECURE_CHANNEL_MAC = 1 15 | SECURE_CHANNEL_MACENC = 3 16 | MAC_LENGTH = 8 17 | 18 | class Cyberflex_Card(Java_Card): 19 | APDU_INITIALIZE_UPDATE = C_APDU('\x80\x50\x00\x00') 20 | APDU_EXTERNAL_AUTHENTICATE = C_APDU('\x84\x82\x00\x00') 21 | APDU_GET_STATUS = C_APDU('\x84\xF2\x00\x00\x02\x4f\x00') 22 | APDU_DELETE = C_APDU('\x84\xe4\x00\x00') 23 | DRIVER_NAME = ["Cyberflex"] 24 | 25 | ATRS = [ 26 | ## Cyberflex Access 32k v2 ??? 27 | ("3B 75 13 00 00 9C 02 02 01 02", 28 | "FF FF FF FF FF FF FF FF FF FF"), 29 | ## Cyberflex Access Developer 32k 30 | ("3B 17 13 9C 12 00 00 00 00 00", 31 | "FF FF 00 FF 00 00 00 00 00 00"), 32 | ## Cyberflex Access e-gate 32K 33 | ("3B 75 94 00 00 62 02 02 00 80", 34 | "FF FF FF 00 00 FF FF FF 00 00"), 35 | ## Cyberflex Access 32K v4 36 | ("3b 76 00 00 00 00 9c 11 01 00 00", 37 | "FF FF FF FF FF FF FF FF FF FF FF"), 38 | ## Cyberflex Access 64K v1 (non-FIPS-compliant, softmask 1.1) 39 | ("3b 75 00 00 00 29 05 01 01 01", 40 | "FF FF 00 00 00 FF FF FF 00 00"), 41 | ## Cyberflex Access 64K v1 (FIPS-compliant, softmask 2.1) 42 | ("3b 75 00 00 00 29 05 01 02 01", 43 | "FF FF 00 00 00 FF FF FF 00 00") 44 | ] 45 | 46 | ## Will convert the ATRS to binary strings 47 | ATRS = [(binascii.a2b_hex("".join(_a.split())), 48 | binascii.a2b_hex("".join(_b.split()))) for (_a,_b) in ATRS] 49 | del _a, _b 50 | 51 | def __init__(self, card = None, keyset = None): 52 | Java_Card.__init__(self, card = card) 53 | 54 | if keyset is not None: 55 | self.keyset = keyset 56 | else: 57 | self.keyset = dict(DEFAULT_KEYSET) 58 | self.card_manager_aid = DEFAULT_CARD_MANAGER_AID 59 | 60 | self.session_key_enc = None 61 | self.session_key_mac = None 62 | self.last_mac = None 63 | self.secure_channel_state = SECURE_CHANNEL_NONE 64 | 65 | def before_send(self, apdu): 66 | """Will be called by send_apdu before sending a command APDU. 67 | Is responsible for authenticating/encrypting commands when needed.""" 68 | if apdu.cla == 0x84: 69 | ## Need security 70 | 71 | if self.secure_channel_state == SECURE_CHANNEL_NONE: 72 | raise Exception, "Need security but channel is not established" 73 | if self.secure_channel_state == SECURE_CHANNEL_CLEAR: 74 | return apdu 75 | elif self.secure_channel_state == SECURE_CHANNEL_MAC: 76 | apdu.lc = apdu.lc + MAC_LENGTH 77 | 78 | mac = crypto_utils.calculate_MAC(self.session_key_mac, apdu.render(), self.last_mac) 79 | self.last_mac = mac 80 | apdu.data = apdu.data + mac 81 | elif self.secure_channel_state == SECURE_CHANNEL_MACENC: 82 | raise Exception, "MAC+Enc Not implemented yet" 83 | return apdu 84 | 85 | def open_secure_channel(self, keyset_version = 0x0, key_index = 0x0, 86 | security_level = SECURE_CHANNEL_MAC): 87 | """Opens a secure channel by sending an InitializeUpdate and 88 | ExternalAuthenticate. 89 | keyset_version is either the explicit key set version or 0x0 for 90 | the implicit key set version. 91 | key_index is either 0x0 for implicit or 0x1 for explicit key index. 92 | security_level is one of SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC 93 | or SECURE_CHANNEL_MACENC. 94 | Note that SECURE_CHANNEL_CLEAR is only available for cards that 95 | are not secured. 96 | 97 | Returns: True on success, generates an exception otherwise. 98 | Warning: Cyberflex Access 64k v2 cards maintain a failure counter 99 | and will lock their key set if they receive 3 InitializeUpdate 100 | commands that are not followed by a successful 101 | ExternalAuthenticate! 102 | If this function does not return True you should not retry 103 | the call, but must closely inspect the situation.""" 104 | 105 | if security_level not in (SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC, SECURE_CHANNEL_MACENC): 106 | raise ValueError, "security_level must be one of SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC or SECURE_CHANNEL_MACENC" 107 | 108 | host_challenge = crypto_utils.generate_host_challenge() 109 | 110 | apdu = C_APDU(self.APDU_INITIALIZE_UPDATE, 111 | p1 = keyset_version, p2 = key_index, 112 | data = host_challenge) 113 | 114 | self.secure_channel_state = SECURE_CHANNEL_NONE 115 | self.last_mac = '\x00' * 8 116 | self.session_key_enc = None 117 | self.session_key_mac = None 118 | 119 | result = self.send_apdu(apdu) 120 | if not self.check_sw(result.sw): 121 | raise Exception, "Statusword after InitializeUpdate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:]) 122 | 123 | card_challenge = result.data[12:20] 124 | card_cryptogram = result.data[20:28] 125 | 126 | self.session_key_enc = crypto_utils.get_session_key( 127 | self.keyset[KEY_AUTH], host_challenge, card_challenge) 128 | self.session_key_mac = crypto_utils.get_session_key( 129 | self.keyset[KEY_MAC], host_challenge, card_challenge) 130 | 131 | if not crypto_utils.verify_card_cryptogram(self.session_key_enc, 132 | host_challenge, card_challenge, card_cryptogram): 133 | raise Exception, "Validation error, card not authenticated. Warning: No successful ExternalAuthenticate; keyset might be locked soon" 134 | 135 | host_cryptogram = crypto_utils.calculate_host_cryptogram( 136 | self.session_key_enc, card_challenge, host_challenge) 137 | 138 | apdu = C_APDU(self.APDU_EXTERNAL_AUTHENTICATE, 139 | p1 = security_level, p2 = 0, 140 | data = host_cryptogram) 141 | 142 | self.secure_channel_state = SECURE_CHANNEL_MAC 143 | result = self.send_apdu(apdu) 144 | self.secure_channel_state = security_level 145 | 146 | if not self.check_sw(result.sw): 147 | self.secure_channel_state = SECURE_CHANNEL_NONE 148 | raise Exception, "Statusword after ExternalAuthenticate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:]) 149 | 150 | return True 151 | 152 | def select_application(self, aid): 153 | result = Java_Card.select_application(self, aid) 154 | if self.check(self.last_sw) and aid[:5] != DEFAULT_CARD_MANAGER_AID[:5]: 155 | self.secure_channel_state = SECURE_CHANNEL_NONE 156 | return result 157 | 158 | def get_status(self, reference_control=0x20): 159 | """Sends a GetStatus APDU und returns the result. 160 | reference_control is either: 161 | 0x20 Load files 162 | 0x40 Applications 163 | 0x60 Applications and load files 164 | 0x80 Card manager 165 | 0xA0 Card manager and load files 166 | 0xC0 Card manager and applications 167 | 0xE0 Card manager, applications and load files. 168 | 169 | Returns: the response APDU which can be parsed with 170 | utils.parse_status()""" 171 | return self.send_apdu( 172 | C_APDU(self.APDU_GET_STATUS, 173 | p1 = reference_control) 174 | ) 175 | 176 | def delete(self, aid): 177 | if aid[:5] == DEFAULT_CARD_MANAGER_AID[:5]: 178 | print "Cowardly refusing to delete the card manager." 179 | raise ValueError, "Undeletable object" 180 | tlvaid = chr(0x4f) + chr(len(aid)) + aid 181 | apdu = C_APDU(self.APDU_DELETE, 182 | data = tlvaid) 183 | result = self.send_apdu(apdu) 184 | 185 | return result.data[0] == 0x0 186 | 187 | def cmd_delete(self, aid): 188 | """Delete the object identified by aid.""" 189 | aid = binascii.a2b_hex("".join(aid.split())) 190 | self.delete(aid) 191 | 192 | def cmd_status(self, reference_control = "0x20"): 193 | """Execute a GetStatus command and return the result.""" 194 | reference_control = int(reference_control, 0) 195 | result = self.get_status(reference_control) 196 | utils.parse_status(result.data) 197 | 198 | def cmd_secure(self, keyset_version=None, key_index=None, security_level=None): 199 | """Open a secure channel. 200 | If given, keyset_version and key_index must be integers while security_level can be one of 0, clear, 1, mac, 3, macenc, mac+enc.""" 201 | if keyset_version is None and key_index is None and security_level is None: 202 | arg1 = 0 203 | arg2 = 0 204 | arg3int = SECURE_CHANNEL_MAC 205 | elif keyset_version is not None and key_index is not None and security_level is not None: 206 | arg1 = int(keyset_version,0) 207 | arg2 = int(key_index,0) 208 | 209 | if arg1 not in range(256): 210 | raise ValueError, "keyset_version must be between 0 and 255 (inclusive)." 211 | if arg2 not in (0,1): 212 | raise ValueError, "key_index must be 0 or 1." 213 | 214 | arg3 = security_level.strip().lower() 215 | try: 216 | arg3int = int(security_level,0) 217 | except: 218 | arg3int = None 219 | 220 | if arg3 == "clear": 221 | arg3int = SECURE_CHANNEL_CLEAR 222 | elif arg3 == "mac": 223 | arg3int = SECURE_CHANNEL_MAC 224 | elif arg3 in ("macenc", "mac+enc"): 225 | arg3int = SECURE_CHANNEL_MACENC 226 | else: 227 | raise TypeError, "Must give none or three arguments." 228 | self.open_secure_channel(arg1, arg2, arg3int) 229 | 230 | def cmd_setkey(self, key_index, key): 231 | """Set a key in the current keyset. 232 | key_index should be one of 0, all, 1, enc, auth, 2, mac, 3, kek.""" 233 | arg1 = key_index.strip().lower() 234 | try: 235 | arg1int = int(arg1,0) 236 | except: 237 | arg1int = None 238 | 239 | if len(key) != 16: 240 | arg2 = binascii.a2b_hex("".join(key.split())) 241 | else: 242 | arg2 = key 243 | 244 | if len(arg2) != 16: 245 | raise TypeError, "Need either exactly 16 binary bytes or 16 hexadezimal bytes for the key argument." 246 | 247 | if arg1int == 0 or arg1 == "all": 248 | all = True 249 | else: 250 | all = False 251 | 252 | if all or arg1int == KEY_AUTH or arg1 in("auth","enc"): 253 | self.keyset[KEY_AUTH] = arg2 254 | if all or arg1int == KEY_MAC or arg1 == "mac": 255 | self.keyset[KEY_MAC] = arg2 256 | if all or arg1int == KEY_KEK or arg1 == "kek": 257 | self.keyset[KEY_KEK] = arg2 258 | 259 | def cmd_printkeyset(self): 260 | """Print the current keyset.""" 261 | print "ENC,AUTH:", utils.hexdump(self.keyset[KEY_AUTH], short=True) 262 | print "MAC: ", utils.hexdump(self.keyset[KEY_MAC], short=True) 263 | print "KEK: ", utils.hexdump(self.keyset[KEY_KEK], short=True) 264 | 265 | def cmd_resetkeyset(self): 266 | """Reset the keyset to the default keyset for this card.""" 267 | self.keyset = dict(DEFAULT_KEYSET) 268 | 269 | def cmd_savekeyset(self, filename): 270 | """Saves the keyset to the named file.""" 271 | fd = file(filename, "w") 272 | fd.write(self.keyset[KEY_AUTH]) 273 | fd.write(self.keyset[KEY_MAC]) 274 | fd.write(self.keyset[KEY_KEK]) 275 | fd.close() 276 | 277 | def cmd_loadkeyset(self, filename): 278 | """Loads the keyset from the named file.""" 279 | fd = file(filename, "r") 280 | keys = fd.read(16*3) 281 | if len(keys) != 16*3: 282 | del keys 283 | fd.close() 284 | raise ValueError, "The file must must contain three keys of 16 bytes each." 285 | self.keyset[KEY_AUTH] = keys[:16] 286 | self.keyset[KEY_MAC] = keys[16:16*2] 287 | self.keyset[KEY_KEK] = keys[16*2:16*3] 288 | del keys 289 | fd.close() 290 | 291 | _secname = {SECURE_CHANNEL_NONE: "", 292 | SECURE_CHANNEL_CLEAR: " [clear]", 293 | SECURE_CHANNEL_MAC: " [MAC]", 294 | SECURE_CHANNEL_MACENC: " [MAC+enc]"} 295 | def get_prompt(self): 296 | return "(%s)%s" % (self.get_driver_name(), 297 | Cyberflex_Card._secname[self.secure_channel_state]) 298 | 299 | 300 | APPLICATIONS = dict(Java_Card.APPLICATIONS) 301 | APPLICATIONS.update( { 302 | DEFAULT_CARD_MANAGER_AID: ("card_manager", ), 303 | } ) 304 | 305 | COMMANDS = dict(Java_Card.COMMANDS) 306 | COMMANDS.update( { 307 | "status": cmd_status, 308 | "open_secure_channel": cmd_secure, 309 | "set_key": cmd_setkey, 310 | "print_keyset": cmd_printkeyset, 311 | "reset_keyset": cmd_resetkeyset, 312 | "save_keyset": cmd_savekeyset, 313 | "load_keyset": cmd_loadkeyset, 314 | "delete": cmd_delete 315 | } ) 316 | STATUS_WORDS = dict(Java_Card.STATUS_WORDS) 317 | STATUS_WORDS.update( { 318 | "\x62\x83": "The Card Manager is locked (SelectApplication).", 319 | "\x63\x00": "Authentication of the host cryptogram failed.", 320 | "\x63\x10": "More data is available for return than is specified in the Le value.", 321 | "\x64\x00": "Technical problem that has no specified diagnosis.", 322 | "\x65\x81": "Memory failure.", 323 | "\x67\x00": "The specified length of the input data (Lc) is incorrect.", 324 | "\x69\x81": "No key is specified (GetResponse, called internally).", 325 | "\x69\x82": "Security status not satisfied. For example, MAC verification failed, the authentication key is locked, or the current security domain requires DAP verification and no verification data was included with the command.", 326 | "\x69\x83": "The key is blocked (GetResponse, called internally).", 327 | "\x69\x85": """A requirement for using the command is not satisfied. For example: 328 | + Command issued outside of a secure channel. 329 | + Current application does not have the required application privilege 330 | or life cycle state. 331 | + The required preceding command was not present. 332 | + The object to delete is referenced by another object on the card.""", 333 | "\x69\x87": "The MAC or other verification data is missing (Install).", 334 | "\x69\x99": "Application selection failed (SelectApplication).", 335 | "\x6A\x80": """Invalid or inconsistent input data, including input data that is inconsistent with a command header parameter, and LV/TLV-format elements in the input data that are not self-consistent. For example: 336 | + Incorrect number of padding bytes, incorrect key used for 337 | encryption, or the specified key set or key index value is invalid. 338 | + Referenced AID is not found in the card registry or package, or the 339 | newly specified AID already exists in the registry. 340 | + Inappropriate application privilege byte value (installing security 341 | domain), or card already has a default selected application 342 | (specifying default selected application). 343 | + First block of input data for a load file is not preceded by the 344 | correct tag and/or valid length, or the load file refers to a 345 | nonexistent package.""", 346 | "\x6A\x81": "Target is locked (SelectApplication).", 347 | "\x6A\x82": "Registry contains no valid application (or no additional valid application) with the specified AID (SelectApplication).", 348 | "\x6A\x84": "Insufficient EEPROM memory available to add the object to the card.", 349 | "\x6A\x86": "Incorrect or unsupported value is specified for P1, P2, or both.", 350 | "\x6A\x88": "Data referred to in P1, P2, or both is not found.", 351 | "\x6D\x00": "Unsupported value entered for the INS byte.", 352 | "\x6E\x00": "Unsupported value entered for the CLA byte.", 353 | "\x6F\x00": "JVM error that has no specified diagnosis.", 354 | "\x90\x00": "Command succeeded.", 355 | "\x94\x81": "Target has an invalid life cycle state.", 356 | "\x94\x84": "Unsupported algorithm ID in input data (PutKey).", 357 | "\x94\x85": "Invalid key check value in input data (PutKey).", 358 | } ) 359 | 360 | if __name__ == "__main__": 361 | c = Cyberflex_Card() 362 | print utils.hexdump( c.select_application(DEFAULT_CARD_MANAGER_AID) ) 363 | 364 | c.open_secure_channel(security_level = SECURE_CHANNEL_MAC) 365 | utils.parse_status(c.get_status(224)[:-2]) 366 | -------------------------------------------------------------------------------- /cards/generic_application.py: -------------------------------------------------------------------------------- 1 | import binascii, re, sys, cards 2 | 3 | class Application: 4 | 5 | # This must be a sequence of regular expressions 6 | # When an application is selected through a matching AID 7 | # then all correponding classes are merged into the card 8 | # object. 9 | # The application classes themselves are responsible for 10 | # unmerging, should their application become deselected. 11 | # However, the default implementation in the generic 12 | # Application class which triggers unmerging on a card reset 13 | # and a successfull SELECT APPLICATION should be sufficient 14 | # in most cases. 15 | # (Still haven't thought this through, though...) 16 | ## FIXME Unloading is not implemented yet 17 | # 18 | # NOTE: Implementing classes MUST derive from Application 19 | AID_LIST = [] 20 | 21 | def load_applications(card, aid): 22 | classes_to_load = [] 23 | for i in dir(cards): 24 | possible_class = getattr(cards, i) 25 | if not hasattr(possible_class, "DRIVER_NAME") or not issubclass(possible_class, Application): 26 | continue 27 | if possible_class.can_handle_aid(card, aid): 28 | classes_to_load.append(possible_class) 29 | print ".oO(Loading application '%s')" % ", ".join(possible_class.DRIVER_NAME) 30 | 31 | card.add_classes(classes_to_load) 32 | load_applications = staticmethod(load_applications) 33 | 34 | def can_handle_aid(cls, card, aid): 35 | for i in cls.AID_LIST: 36 | if re.match(i+"$", binascii.b2a_hex(aid), re.I): 37 | return True 38 | return False 39 | can_handle_aid = classmethod(can_handle_aid) 40 | -------------------------------------------------------------------------------- /cards/generic_card.py: -------------------------------------------------------------------------------- 1 | import smartcard 2 | import TLV_utils, crypto_utils, utils, binascii, fnmatch, re, time 3 | from utils import C_APDU, R_APDU 4 | 5 | DEBUG = True 6 | #DEBUG = False 7 | 8 | 9 | _GENERIC_NAME = "Generic" 10 | class Card: 11 | DRIVER_NAME = [_GENERIC_NAME] 12 | COMMAND_GET_RESPONSE = None 13 | 14 | ## Constants for check_sw() 15 | PURPOSE_SUCCESS = 1 # Command executed successful 16 | PURPOSE_GET_RESPONSE = 2 # Command executed successful but needs GET RESPONSE with correct length 17 | PURPOSE_SM_OK = 3 # Command not executed successful or with warnings, but response still contains SM objects 18 | PURPOSE_RETRY = 4 # Command would be executed successful but needs retry with correct length 19 | 20 | ## Map for check_sw() 21 | STATUS_MAP = {} 22 | 23 | ## Note: an item in this list must be a tuple of (atr, mask) where atr is a binary 24 | ## string and mask a binary mask. Alternatively mask may be None, then ATR must be a regex 25 | ## to match on the ATRs hexlify representation 26 | ATRS = [] 27 | ## Note: A list of _not_ supported ATRs, overriding any possible match in ATRS. Matching 28 | ## is done as for ATRS. 29 | STOP_ATRS = [] 30 | 31 | ## Note: a key in this dictionary may either be a one- or two-byte string containing 32 | ## a binary status word, or a two or four-byte string containing a hexadecimal 33 | ## status word, possibly with ? characters marking variable nibbles. 34 | ## Hexadecimal characters MUST be in uppercase. The values that two- or four-byte 35 | ## strings map to may be either format strings, that can make use of the 36 | ## keyword substitutions for SW1 and SW2 or a callable accepting two arguments 37 | ## (SW1, SW2) that returns a string. 38 | STATUS_WORDS = { 39 | } 40 | ## For the format of this dictionary of dictionaries see TLV_utils.tags 41 | TLV_OBJECTS = {} 42 | DEFAULT_CONTEXT = None 43 | 44 | ## Format: "AID (binary)": ("name", [optional: description, {more information}]) 45 | APPLICATIONS = { 46 | } 47 | 48 | ## Format: "RID (binary)": ("vendor name", [optional: {more information}]) 49 | VENDORS = { 50 | } 51 | 52 | def _decode_df_name(self, value): 53 | result = " " + utils.hexdump(value, short=True) 54 | info = None 55 | 56 | if self.APPLICATIONS.has_key(value): 57 | info = self.APPLICATIONS[value] 58 | else: 59 | for aid, i in self.APPLICATIONS.items(): 60 | if not len(i) > 2 or not i[2].has_key("significant_length"): 61 | continue 62 | if aid[ :i[2]["significant_length"] ] == value[ :i[2]["significant_length"] ]: 63 | info = i 64 | break 65 | 66 | result_array = [] 67 | if info is not None: 68 | if info[0] is not None: 69 | result_array.append( ("Name", info[0]) ) 70 | 71 | if len(info) > 1 and not info[1] is None: 72 | result_array.append( ("Description", info[1] ) ) 73 | 74 | if self.VENDORS.has_key(value[:5]): 75 | result_array.append( ("Vendor", self.VENDORS[ value[:5] ][0]) ) 76 | 77 | if len(result_array) > 0: 78 | max_len = max( [len(a) for a,b in result_array] + [11] ) + 1 79 | result = result + "\n" + "\n".join( [("%%-%is %%s" % max_len) % (a+":",b) for a,b in result_array] ) 80 | return result 81 | 82 | def decode_df_name(value): 83 | # Static method for when there is no object reference 84 | return Card._decode_df_name(value) 85 | 86 | def __init__(self, reader): 87 | self.reader = reader 88 | 89 | self._i = 0 90 | self.last_apdu = None 91 | self.last_result = None 92 | self._last_start = None 93 | self.last_delta = None 94 | 95 | def cmd_parsetlv(self, start = None, end = None): 96 | "Decode the TLV data in the last response, start and end are optional" 97 | lastlen = len(self.last_result.data) 98 | if start is not None: 99 | start = (lastlen + (int(start,0) % lastlen) ) % lastlen 100 | else: 101 | start = 0 102 | if end is not None: 103 | end = (lastlen + (int(end,0) % lastlen) ) % lastlen 104 | else: 105 | end = lastlen 106 | print TLV_utils.decode(self.last_result.data[start:end], tags=self.TLV_OBJECTS, context = self.DEFAULT_CONTEXT) 107 | 108 | _SHOW_APPLICATIONS_FORMAT_STRING = "%(aid)-50s %(name)-20s %(description)-30s" 109 | def cmd_show_applications(self): 110 | "Show the list of known (by the shell) applications" 111 | print self._SHOW_APPLICATIONS_FORMAT_STRING % {"aid": "AID", "name": "Name", "description": "Description"} 112 | foo = self.APPLICATIONS.items() 113 | foo.sort() 114 | for aid, info in foo: 115 | print self._SHOW_APPLICATIONS_FORMAT_STRING % { 116 | "aid": utils.hexdump(aid, short=True), 117 | "name": info[0], 118 | "description": len(info) > 1 and info[1] or "" 119 | } 120 | 121 | def cmd_reset(self): 122 | """Reset the card.""" 123 | # FIXME 124 | raise NotImplementedException 125 | 126 | COMMANDS = { 127 | "reset": cmd_reset, 128 | "parse_tlv": cmd_parsetlv, 129 | "show_applications": cmd_show_applications, 130 | } 131 | 132 | def _real_send(self, apdu): 133 | apdu_binary = apdu.render() 134 | 135 | if DEBUG: 136 | print ">> " + utils.hexdump(apdu_binary, indent = 3) 137 | 138 | result_binary = self.reader.transceive(apdu_binary) 139 | result = apdu.RESPONSE_CLASS(result_binary) 140 | 141 | self.last_apdu = apdu 142 | 143 | if DEBUG: 144 | print "<< " + utils.hexdump(result_binary, indent = 3) 145 | return result 146 | 147 | def _send_with_retry(self, apdu): 148 | result = self._real_send(apdu) 149 | 150 | while self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE): 151 | ## Need to call GetResponse 152 | gr_apdu = self.COMMAND_GET_RESPONSE 153 | tmp = self._real_send(gr_apdu) 154 | 155 | if not callable(result.append): 156 | result = tmp 157 | break 158 | else: 159 | result = result.append(tmp) 160 | 161 | return result 162 | 163 | def send_apdu(self, apdu): 164 | if DEBUG: 165 | print "%s\nBeginning transaction %i" % ('-'*80, self._i) 166 | 167 | self.last_delta = None 168 | self._last_start = time.time() 169 | 170 | if hasattr(self, "before_send"): 171 | apdu = self.before_send(apdu) 172 | 173 | result = self._send_with_retry(apdu) 174 | 175 | if hasattr(self, "after_send"): 176 | result = self.after_send(result) 177 | 178 | if self._last_start is not None: 179 | self.last_delta = time.time() - self._last_start 180 | self._last_start = None 181 | 182 | if DEBUG: 183 | print "Ending transaction %i\n%s\n" % (self._i, '-'*80) 184 | self._i = self._i + 1 185 | 186 | self.last_result = result 187 | return result 188 | 189 | def check_sw(self, sw, purpose = None): 190 | if purpose is None: purpose = Card.PURPOSE_SUCCESS 191 | return self.match_statusword(self.STATUS_MAP[purpose], sw) 192 | 193 | def _get_atr(reader): 194 | return reader.get_ATR() 195 | _get_atr = staticmethod(_get_atr) 196 | 197 | def get_atr(self): 198 | return self._get_atr(self.reader) 199 | 200 | def can_handle(cls, reader): 201 | """Determine whether this class can handle a given card/connection object.""" 202 | ATR = cls._get_atr(reader) 203 | def match_list(atr, list): 204 | for (knownatr, mask) in list: 205 | if mask is None: 206 | if re.match(knownatr, binascii.hexlify(atr), re.I): 207 | return True 208 | else: 209 | if len(knownatr) != len(atr): 210 | continue 211 | if crypto_utils.andstring(knownatr, mask) == crypto_utils.andstring(atr, mask): 212 | return True 213 | return False 214 | 215 | if not match_list(ATR, cls.STOP_ATRS) and match_list(ATR, cls.ATRS): 216 | return True 217 | return False 218 | 219 | can_handle = classmethod(can_handle) 220 | 221 | def get_prompt(self): 222 | return "(%s)" % self.get_driver_name() 223 | 224 | def match_statusword(swlist, sw): 225 | """Try to find sw in swlist. 226 | swlist must be a list of either binary statuswords (two bytes), hexadecimal statuswords (four bytes) or fnmatch patterns on a hexadecimal statusword. 227 | Returns: The element that matched (either two bytes, four bytes or the fnmatch pattern).""" 228 | if sw in swlist: 229 | return sw 230 | sw = binascii.hexlify(sw).upper() 231 | if sw in swlist: 232 | return sw 233 | for value in swlist: 234 | if fnmatch.fnmatch(sw, value): 235 | return value 236 | return None 237 | match_statusword = staticmethod(match_statusword) 238 | 239 | def get_driver_name(self): 240 | if len(self.DRIVER_NAME) > 1: 241 | names = [e for e in self.DRIVER_NAME if e != _GENERIC_NAME] 242 | else: 243 | names = self.DRIVER_NAME 244 | return ", ".join(names) 245 | 246 | def close_card(self): 247 | "Disconnect from a card" 248 | self.reader.disconnect() 249 | del self.reader 250 | 251 | -------------------------------------------------------------------------------- /cards/gsm_card.py: -------------------------------------------------------------------------------- 1 | import utils 2 | from iso_card import * 3 | 4 | class GSM_Card(ISO_Card): 5 | DRIVER_NAME = ["GSM"] 6 | COMMAND_GET_RESPONSE = C_APDU("\xa0\xC0\x00\x00") 7 | 8 | STATUS_MAP = { 9 | Card.PURPOSE_GET_RESPONSE: ("9F??", ) 10 | } 11 | 12 | ATRS = [ 13 | ("3bff9500ffc00a1f438031e073f62113574a334861324147d6", None), 14 | ] 15 | 16 | STATUS_WORDS = { 17 | '9F??': "Length '%(SW2)i (0x%(SW2)02x)' of the response data", 18 | '920?': lambda sw1, sw2: "Update successful but after using an internal retry routine '%i' times" % (sw2 % 16), 19 | '9240': "Memory problem", 20 | '9400': "No EF selected", 21 | '9402': "Out of range (invalid address)", 22 | '9404': "- File ID not found\n- Pattern not found", 23 | '9408': "File is inconsistent with the command", 24 | '9802': "No CHV initialized", 25 | '9804': "- Access condition not fulfilled\n- Unsuccessful CHV verification, at least one attempt left\n- unsuccesful UNBLOCK CHV verification, at least one attempt left\n- authentication failed", 26 | '9808': "In contradiction with CHV status", 27 | '9810': "In contradiction with invalidation status", 28 | '9840': "- Unsuccessful CHV verification, no attempt left\n- unsuccesful UNBLOCK CHV verification, no attempt left\n- CHV blocked\n- UNBLOCK CHV blocked", 29 | '9850': "Increase cannot be performed, Max value reached", 30 | "67??": "Incorrect parameter P3", 31 | "\x67\x00": "Incorrect parameter P3 (ISO:Wrong length)", 32 | "6B??": "Incorrect parameter P1 or P2", 33 | "\x6B\x00": "Incorrect parameter P1 or P2 (ISO:Wrong parameter(s) P1-P2)", 34 | "6D??": "Unknown instruction code given in the command", 35 | "\x6D\x00": "Unknown instruction code given in the command (ISO: Instruction code not supported or invalid)", 36 | "6E??": "Wrong instruction class given in the command", 37 | "\x6E\x00": "Wrong instruction class given in the command (ISO: Class not supported)", 38 | "6F??": "Technical problem with no diagnostic given", 39 | "\x6F\x00": "Technical problem with no diagnostic given (ISO: No precise diagnosis)", 40 | 41 | } 42 | -------------------------------------------------------------------------------- /cards/iso_7816_4_card.py: -------------------------------------------------------------------------------- 1 | import sys;sys.path.append(".."); sys.path.append(".") 2 | import TLV_utils 3 | from iso_card import * 4 | from generic_application import Application 5 | import building_blocks 6 | 7 | class iso_node(object): 8 | SORT_NONE = False 9 | SORT_NORMAL = True 10 | SORT_DFFIRST = object() # Random object, just for identity testing 11 | 12 | def __init__(self, parent=None, management_information=None, card_object=None, generic_description=None): 13 | self._parent = parent 14 | self._management_information = management_information # FCI, FCP or FMD 15 | self._card_object = card_object 16 | self._children = [] 17 | self._generic_description = generic_description # Note: Only used for iso_node, not for iso_ef or iso_df 18 | 19 | if self._parent is not None: 20 | self._parent.add_child(self) 21 | if self._card_object is None and self._parent._card_object is not None: 22 | self._card_object = self._parent._card_object 23 | 24 | def print_node(self, indent=0, stream=None, stringlist=None, **kwargs): 25 | result = self._format_node(indent, **kwargs) 26 | if stream is not None: 27 | stream.write("\n".join(result)) 28 | elif stringlist is not None: 29 | stringlist.extend(result) 30 | else: 31 | print "\n".join(result) 32 | 33 | if kwargs.get("recurse", True): 34 | tosort = kwargs.get("sort", self.SORT_NONE) 35 | 36 | def cmp_normal(a,b): return cmp(a.fid,b.fid) 37 | def cmp_dffirst(a,b): 38 | return (a.__class__ != b.__class__) and (a.__class__ == iso_df and -1 or 1) or cmp(a.fid,b.fid) 39 | 40 | if tosort is self.SORT_DFFIRST: 41 | for child in sorted(self._children, cmp=cmp_dffirst): 42 | child.print_node(indent+1, **kwargs) 43 | elif tosort: 44 | for child in sorted(self._children, cmp=cmp_normal): 45 | child.print_node(indent+1, **kwargs) 46 | else: 47 | for child in self._children: 48 | child.print_node(indent+1, **kwargs) 49 | 50 | def _dump_internal(self, data, indent, do_tlv=True): 51 | c = utils.hexdump(data) 52 | r = map(lambda a: self.get_indent(indent)+a, c.splitlines(False)) 53 | if do_tlv: 54 | try: 55 | if self._card_object is not None: 56 | c = TLV_utils.decode(data, tags=self._card_object.TLV_OBJECTS, context = self._card_object.DEFAULT_CONTEXT) 57 | else: 58 | c = TLV_utils.decode(data) 59 | r.append( self.get_indent(indent) + "Trying TLV parse:" ) 60 | r.extend( map(lambda a: self.get_indent(indent)+a, c.splitlines(False)) ) 61 | except (SystemExit, KeyboardInterrupt): 62 | raise 63 | except: 64 | pass 65 | return r 66 | 67 | def _format_node(self, indent, **kwargs): 68 | result = [] 69 | if self._generic_description: 70 | result.append(self.get_indent(indent) + "+ " + self._generic_description) 71 | else: 72 | result.append(self.get_indent(indent) + "+ Generic Node") 73 | if kwargs.get("with_management_information", True): 74 | result.extend(self._format_management_information(indent)) 75 | return result 76 | 77 | def _format_management_information(self, indent): 78 | result = [] 79 | if self._management_information is None: return result 80 | 81 | try: 82 | if self._card_object is not None: 83 | c = TLV_utils.decode(self._management_information, tags=self._card_object.TLV_OBJECTS, context = self._card_object.DEFAULT_CONTEXT) 84 | else: 85 | c = TLV_utils.decode(self._management_information) 86 | result.append(self.get_indent(indent+1) + "Management information:") 87 | result.extend( map(lambda a: self.get_indent(indent+2)+a, c.splitlines(False)) ) 88 | except (SystemExit, KeyboardInterrupt): 89 | raise 90 | except: 91 | result.append(self.get_indent(indent+1) + "Raw dump of unparseable management information following:") 92 | result.extend(self._dump_internal(self._management_information, indent=indent+2, do_tlv=False)) 93 | 94 | return result 95 | 96 | def add_child(self, node): 97 | raise NotImplementedError, "Can't add a child to a mere node" 98 | 99 | def get_fid(self): return self._fid 100 | def get_parent(self): return self._parent 101 | 102 | fid = property(get_fid) 103 | parent = property(get_parent) 104 | 105 | @staticmethod 106 | def get_indent(indent): 107 | return "\t"*indent 108 | 109 | class iso_ef(iso_node): 110 | TYPE_TRANSPARENT = 1 111 | TYPE_RECORD = 2 112 | 113 | def __init__(self, fid, type=None, **kwargs): 114 | super(iso_ef, self).__init__(**kwargs) 115 | self._fid = fid 116 | self._content = None 117 | self._type = type 118 | 119 | def _format_node(self, indent, **kwargs): 120 | result = [self.get_indent(indent) + "+ EF: %s" % utils.hexdump(self.fid, short=True)] 121 | 122 | if kwargs.get("with_management_information", True): 123 | result.extend(self._format_management_information(indent)) 124 | 125 | if kwargs.get("with_content", True) and self._content is not None: 126 | if self._type is self.TYPE_TRANSPARENT: 127 | result.append(self.get_indent(indent+1) + 128 | "Contents (length: %i (0x%0X)):" % (len(self._content),len(self._content)) 129 | ) 130 | result.extend(self._dump_internal(self._content,indent=indent+2)) 131 | elif self._type is self.TYPE_RECORD: 132 | result.append(self.get_indent(indent+1) + "%i (0x0%X) records following:" % (len(self._content),len(self._content)) ) 133 | 134 | for i,d in enumerate(self._content): 135 | result.append(self.get_indent(indent+2) + 136 | "Record %i (length: %i (0x%0X)):" % (i, len(d),len(d)) 137 | ) 138 | result.extend(self._dump_internal(d,indent=indent+3)) 139 | else: 140 | result.append(self.get_indent(indent+1) + "Contents:") 141 | result.append(self.get_indent(indent+2) + repr(self._content)) 142 | return result 143 | 144 | class iso_df(iso_node): 145 | def __init__(self, fid, **kwargs): 146 | super(iso_df, self).__init__(**kwargs) 147 | self._fid = fid 148 | 149 | def _format_node(self, indent, **kwargs): 150 | result = [self.get_indent(indent) + "+ DF: %s" % utils.hexdump(self.fid, short=True)] 151 | if kwargs.get("with_management_information", True): 152 | result.extend(self._format_management_information(indent)) 153 | return result 154 | 155 | def add_child(self, node): 156 | if node not in self._children: 157 | self._children.append(node) 158 | 159 | class ISO_7816_4_Card(building_blocks.Card_with_read_binary,ISO_Card): 160 | APDU_SELECT_APPLICATION = C_APDU(ins=0xa4,p1=0x04) 161 | APDU_SELECT_FILE = C_APDU(ins=0xa4, le=0) 162 | APDU_READ_BINARY = C_APDU(ins=0xb0,le=0) 163 | APDU_READ_RECORD = C_APDU(ins=0xb2,le=0) 164 | DRIVER_NAME = ["ISO 7816-4"] 165 | FID_MF = "\x3f\x00" 166 | 167 | SELECT_FILE_P1 = 0x02 168 | SELECT_P2 = 0x0 169 | SELECT_FILE_LE = None 170 | 171 | EF_CLASS = iso_ef 172 | DF_CLASS = iso_df 173 | 174 | ## def can_handle(cls, card): 175 | ## return True 176 | ## can_handle = classmethod(can_handle) 177 | 178 | def select_file(self, p1, p2, fid): 179 | result = self.send_apdu( 180 | C_APDU(self.APDU_SELECT_FILE, 181 | p1 = p1, p2 = p2, 182 | data = fid, le = self.SELECT_FILE_LE) ) 183 | return result 184 | 185 | def change_dir(self, fid = None): 186 | "Change to a child DF. Alternatively, change to MF if fid is None." 187 | if fid is None: 188 | return self.select_file(0x00, self.SELECT_P2, "") 189 | else: 190 | return self.select_file(0x01, self.SELECT_P2, fid) 191 | 192 | def cmd_cd(self, dir = None): 193 | "Change into a DF, or into the MF if no dir is given" 194 | 195 | if dir is None: 196 | result = self.change_dir() 197 | else: 198 | fid = binascii.a2b_hex("".join(dir.split())) 199 | result = self.change_dir(fid) 200 | 201 | if len(result.data) > 0: 202 | print utils.hexdump(result.data) 203 | print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS) 204 | 205 | def open_file(self, fid, p2 = None): 206 | "Open an EF under the current DF" 207 | if p2 is None: p2 = self.SELECT_P2 208 | return self.select_file(self.SELECT_FILE_P1, p2, fid) 209 | 210 | def cmd_open(self, file): 211 | "Open a file" 212 | fid = binascii.a2b_hex("".join(file.split())) 213 | 214 | result = self.open_file(fid) 215 | if len(result.data) > 0: 216 | print utils.hexdump(result.data) 217 | print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS) 218 | 219 | def read_record(self, p1 = 0, p2 = 0, le = 0): 220 | "Read a record from the currently selected file" 221 | command = C_APDU(self.APDU_READ_RECORD, p1 = p1, p2 = p2, le = le) 222 | result = self.send_apdu(command) 223 | return result.data 224 | 225 | def cmd_read_record(self, p1 = None, p2 = None, le = "0"): 226 | "Read a record" 227 | if p1 is None and p2 is None: 228 | p1 = p2 = "0" 229 | elif p2 is None: 230 | p2 = "0x04" # Use record number in P1 231 | contents = self.read_record(p1 = int(p1,0), p2 = int(p2,0), le = int(le,0)) 232 | print utils.hexdump(contents) 233 | 234 | def cmd_next_record(self, le = "0"): 235 | "Read the next record" 236 | return self.cmd_read_record(p1 = "0", p2 = "2", le = le) 237 | 238 | def cmd_selectfile(self, p1, p2, fid): 239 | """Select a file on the card.""" 240 | 241 | p1 = binascii.a2b_hex("".join(p1.split())) 242 | p2 = binascii.a2b_hex("".join(p2.split())) 243 | fid = binascii.a2b_hex("".join(fid.split())) 244 | 245 | result = self.select_file(p1, p2, fid) 246 | if len(result.data) > 0: 247 | print utils.hexdump(result.data) 248 | print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS) 249 | 250 | def select_application(self, aid, le=0, **kwargs): 251 | result = self.send_apdu( 252 | C_APDU(self.APDU_SELECT_APPLICATION, 253 | data = aid, le = le, **kwargs) ) ## FIXME With or without le 254 | if self.check_sw(result.sw): 255 | Application.load_applications(self, aid) 256 | return result 257 | 258 | def resolve_symbolic_aid(self, symbolic_name): 259 | "Returns symbolic_name, or if symbolic_name is a known symbolic_name then its corresponding aid." 260 | s = [a for a,b in self.APPLICATIONS.items() 261 | if (b[0] is not None and b[0].lower() == symbolic_name.lower()) 262 | or (len(b) > 2 and symbolic_name.lower() in [c.lower() for c in b[2].get("alias", [])]) 263 | ] 264 | if len(s) > 0: 265 | aid = s[0] 266 | else: 267 | aid = binascii.a2b_hex("".join(symbolic_name.split())) 268 | 269 | return aid 270 | 271 | def cmd_selectapplication(self, application): 272 | """Select an application on the card. 273 | application can be given either as hexadecimal aid or by symbolic name (if known).""" 274 | 275 | aid = self.resolve_symbolic_aid(application) 276 | 277 | result = self.select_application(aid) 278 | if len(result.data) > 0: 279 | print utils.hexdump(result.data) 280 | print TLV_utils.decode(result.data,tags=self.TLV_OBJECTS) 281 | 282 | def cmd_pretendapplication(self, application): 283 | "Pretend that an application has been selected on the card without actually sending a SELECT APPLICATION. Basically for debugging purposes." 284 | aid = self.resolve_symbolic_aid(application) 285 | Application.load_applications(self, aid) 286 | 287 | ATRS = list(ISO_Card.ATRS) 288 | ATRS.extend( [ 289 | (".*", None), ## For now we accept any card 290 | ] ) 291 | 292 | STOP_ATRS = list(ISO_Card.STOP_ATRS) 293 | STOP_ATRS.extend( [ 294 | ("3b8f8001804f0ca000000306......00000000..", None), # Contactless storage cards (PC/SC spec part 3 section 3.1.3.2.3 295 | ("3b8180018080", None), # Mifare DESfire (special case of contactless smartcard, ibid.) 296 | ] ) 297 | 298 | COMMANDS = dict(ISO_Card.COMMANDS) 299 | COMMANDS.update(building_blocks.Card_with_read_binary.COMMANDS) 300 | COMMANDS.update( { 301 | "select_application": cmd_selectapplication, 302 | "pretend_application": cmd_pretendapplication, 303 | "select_file": cmd_selectfile, 304 | "cd": cmd_cd, 305 | "open": cmd_open, 306 | "read_record": cmd_read_record, 307 | "next_record": cmd_next_record, 308 | } ) 309 | 310 | STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) 311 | STATUS_WORDS.update( { 312 | "62??": "Warning, State of non-volatile memory unchanged", 313 | "63??": "Warning, State of non-volatile memory changed", 314 | "64??": "Error, State of non-volatile memory unchanged", 315 | "65??": "Error, State of non-volatile memory changed", 316 | "66??": "Reserved for security-related issues", 317 | "6700": "Wrong length", 318 | "68??": "Functions in CLA not supported", 319 | "69??": "Command not allowed", 320 | "6A??": "Wrong parameter(s) P1-P2", 321 | "6B00": "Wrong parameter(s) P1-P2", 322 | "6D00": "Instruction code not supported or invalid", 323 | "6E00": "Class not supported", 324 | "6F00": "No precise diagnosis", 325 | 326 | "6200": "Warning, State of non-volatile memory unchanged, No information given", 327 | "6281": "Warning, State of non-volatile memory unchanged, Part of returned data may be corrupted", 328 | "6282": "Warning, State of non-volatile memory unchanged, End of file/record reached before reading Le bytes", 329 | "6283": "Warning, State of non-volatile memory unchanged, Selected file invalidated", 330 | "6284": "Warning, State of non-volatile memory unchanged, FCI not formatted according to ISO-7816-4 5.1.5", 331 | 332 | "6300": "Warning, State of non-volatile memory changed, No information given", 333 | "6381": "Warning, State of non-volatile memory changed, File filled up by the last write", 334 | "63C?": lambda SW1,SW2: "Warning, State of non-volatile memory changed, Counter provided by '%i'" % (SW2%16), 335 | 336 | "6500": "Error, State of non-volatile memory changed, No information given", 337 | "6581": "Error, State of non-volatile memory changed, Memory failure", 338 | 339 | "6800": "Functions in CLA not supported, No information given", 340 | "6881": "Functions in CLA not supported, Logical channel not supported", 341 | "6882": "Functions in CLA not supported, Secure messaging not supported", 342 | 343 | "6900": "Command not allowed, No information given", 344 | "6981": "Command not allowed, Command incompatible with file structure", 345 | "6982": "Command not allowed, Security status not satisfied", 346 | "6983": "Command not allowed, Authentication method blocked", 347 | "6984": "Command not allowed, Referenced data invalidated", 348 | "6985": "Command not allowed, Conditions of use not satisfied", 349 | "6986": "Command not allowed, Command not allowed (no current EF)", 350 | "6987": "Command not allowed, Expected SM data objects missing", 351 | "6988": "Command not allowed, SM data objects incorrect", 352 | 353 | "6A00": "Wrong parameter(s) P1-P2, No information given", 354 | "6A80": "Wrong parameter(s) P1-P2, Incorrect parameters in the data field", 355 | "6A81": "Wrong parameter(s) P1-P2, Function not supported", 356 | "6A82": "Wrong parameter(s) P1-P2, File not found", 357 | "6A83": "Wrong parameter(s) P1-P2, Record not found", 358 | "6A84": "Wrong parameter(s) P1-P2, Not enough memory space in the file", 359 | "6A85": "Wrong parameter(s) P1-P2, Lc inconsistent with TLV structure", 360 | "6A86": "Wrong parameter(s) P1-P2, Incorrect parameters P1-P2", 361 | "6A87": "Wrong parameter(s) P1-P2, Lc inconsistent with P1-P2", 362 | "6A88": "Wrong parameter(s) P1-P2, Referenced data not found", 363 | } ) 364 | 365 | TLV_OBJECTS = TLV_utils.tags 366 | 367 | if __name__ == "__main__": 368 | 369 | root = iso_df('\x3f\x00') 370 | ef_one = iso_ef('\xb0\x01', parent=root, type=iso_ef.TYPE_TRANSPARENT, management_information="he") 371 | ef_two = iso_ef('\xa0\x00', parent=root, type=iso_ef.TYPE_RECORD, management_information="h") 372 | df_one = iso_df('\xa0\x01', parent=root) 373 | df_two = iso_df('\xa0\x00', parent=df_one) 374 | ef_three = iso_ef('\x00\x03', parent=df_one, type=iso_ef.TYPE_RECORD) 375 | ef_four = iso_ef('\x00\x04', parent=df_one) 376 | ef_five = iso_ef('\x00\x05', parent=df_two) 377 | df_three = iso_df('\xa0\x00', parent=df_one) 378 | 379 | ef_one._content = "Foobaluhahihohoahajabla" 380 | ef_two._content = ("bla", "bli", "blu") 381 | ef_three._content = ("Foobaluhahihohoahajabla",) 382 | 383 | root.print_node(sort=root.SORT_DFFIRST) 384 | -------------------------------------------------------------------------------- /cards/iso_card.py: -------------------------------------------------------------------------------- 1 | import smartcard 2 | import TLV_utils, crypto_utils, utils, binascii, fnmatch, re, time 3 | from generic_card import Card 4 | from utils import C_APDU, R_APDU 5 | 6 | class ISO_Card(Card): 7 | DRIVER_NAME = ["ISO"] 8 | COMMAND_GET_RESPONSE = C_APDU(ins=0xc0) 9 | COMMAND_CLASS = C_APDU 10 | 11 | APDU_VERIFY_PIN = C_APDU(ins=0x20) 12 | 13 | ## Map for check_sw() 14 | STATUS_MAP = { 15 | Card.PURPOSE_SUCCESS: ("\x90\x00", ), 16 | Card.PURPOSE_GET_RESPONSE: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2 17 | Card.PURPOSE_SM_OK: ("\x90\x00",), 18 | Card.PURPOSE_RETRY: (), ## Theoretically this would contain "6C??", but I dare not automatically resending a command for _all_ card types 19 | ## Instead, card types for which this is safe should set it in their own STATUS_MAP 20 | } 21 | 22 | ATRS = list(Card.ATRS) 23 | STOP_ATRS = list(Card.STOP_ATRS) 24 | 25 | ## Note: a key in this dictionary may either be a one- or two-byte string containing 26 | ## a binary status word, or a two or four-byte string containing a hexadecimal 27 | ## status word, possibly with ? characters marking variable nibbles. 28 | ## Hexadecimal characters MUST be in uppercase. The values that two- or four-byte 29 | ## strings map to may be either format strings, that can make use of the 30 | ## keyword substitutions for SW1 and SW2 or a callable accepting two arguments 31 | ## (SW1, SW2) that returns a string. 32 | STATUS_WORDS = { 33 | '\x90\x00': "Normal execution", 34 | '61??': "%(SW2)i (0x%(SW2)02x) bytes of response data can be retrieved with GetResponse.", 35 | '6C??': "Bad value for LE, 0x%(SW2)02x is the correct value.", 36 | '63C?': lambda SW1,SW2: "The counter has reached the value '%i'" % (SW2%16) 37 | } 38 | ## For the format of this dictionary of dictionaries see TLV_utils.tags 39 | TLV_OBJECTS = dict(Card.TLV_OBJECTS) 40 | DEFAULT_CONTEXT = None 41 | 42 | ## Format: "AID (binary)": ("name", [optional: description, {more information}]) 43 | APPLICATIONS = { 44 | "\xa0\x00\x00\x01\x67\x45\x53\x49\x47\x4e": ("DF.ESIGN", ), 45 | "\xa0\x00\x00\x00\x63\x50\x4b\x43\x53\x2d\x31\x35": ("DF_PKCS15", ), 46 | "\xD2\x76\x00\x01\x24\x01": ("DF_OpenPGP", "OpenPGP card", {"significant_length": 6} ), 47 | "\xa0\x00\x00\x02\x47\x10\x01": ("DF_LDS", "Machine Readable Travel Document", {"alias": ("mrtd",)}), 48 | ## The following are from 0341a.pdf: BSI-DSZ-CC-0341-2006 49 | "\xD2\x76\x00\x00\x66\x01": ("DF_SIG", "Signature application", {"fid": "\xAB\x00"}), 50 | "\xD2\x76\x00\x00\x25\x5A\x41\x02\x00": ("ZA_MF_NEU", "Zusatzanwendungen", {"fid": "\xA7\x00"}), 51 | "\xD2\x76\x00\x00\x25\x45\x43\x02\x00": ("DF_EC_CASH_NEU", "ec-Cash", {"fid": "\xA1\x00"}), 52 | "\xD2\x76\x00\x00\x25\x45\x50\x02\x00": ("DF_BOERSE_NEU", "Geldkarte", {"fid": "\xA2\x00", "alias": ("geldkarte",)}), 53 | "\xD2\x76\x00\x00\x25\x47\x41\x01\x00": ("DF_GA_MAESTRO", "GA-Maestro", {"fid": "\xAC\x00"}), 54 | "\xD2\x76\x00\x00\x25\x54\x44\x01\x00": ("DF_TAN", "TAN-Anwendung", {"fid": "\xAC\x02"}), 55 | "\xD2\x76\x00\x00\x25\x4D\x01\x02\x00": ("DF_MARKTPLATZ_NEU", "Marktplatz", {"fid": "\xB0\x01"}), 56 | "\xD2\x76\x00\x00\x25\x46\x53\x02\x00": ("DF_FAHRSCHEIN_NEU", "Fahrschein", {"fid": "\xB0\x00"}), 57 | "\xD2\x76\x00\x00\x25\x48\x42\x02\x00": ("DF_BANKING_20" , "HBCI", {"fid": "\xA6\x00"}), 58 | "\xD2\x76\x00\x00\x25\x4E\x50\x01\x00": ("DF_NOTEPAD", "Notepad", {"fid": "\xA6\x10"}), 59 | 60 | "\xd2\x76\x00\x00\x85\x01\x00": ("NFC_TYPE_4", "NFC NDEF Application on tag type 4", {"alias": ("nfc",)}, ), 61 | 62 | # From TR-03110_v201_pdf.pdf 63 | "\xE8\x07\x04\x00\x7f\x00\x07\x03\x02": ("DF_eID", "eID application"), 64 | 65 | "\xd2\x76\x00\x00\x25\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00", "alias": ("vrs",)}, ), 66 | "\xd2\x76\x00\x01\x35\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00",}, ), 67 | } 68 | # Alias for DF_BOERSE_NEU 69 | APPLICATIONS["\xA0\x00\x00\x00\x59\x50\x41\x43\x45\x01\x00"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x45\x50\x02\x00"] 70 | # Alias for DF_GA_MAESTRO 71 | APPLICATIONS["\xA0\x00\x00\x00\x04\x30\x60"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x47\x41\x01\x00"] 72 | 73 | ## Format: "RID (binary)": ("vendor name", [optional: {more information}]) 74 | VENDORS = { 75 | "\xD2\x76\x00\x01\x24": ("Free Software Foundation Europe", ), 76 | "\xD2\x76\x00\x00\x25": ("Bankenverlag", ), 77 | "\xD2\x76\x00\x00\x60": ("Wolfgang Rankl", ), 78 | "\xD2\x76\x00\x00\x05": ("Giesecke & Devrient", ), 79 | "\xD2\x76\x00\x00\x40": ("Zentralinstitut fuer die Kassenaerztliche Versorgung in der Bundesrepublik Deutschland", ), # hpc-use-cases-01.pdf 80 | "\xa0\x00\x00\x02\x47": ("ICAO", ), 81 | "\xa0\x00\x00\x03\x06": ("PC/SC Workgroup", ), 82 | } 83 | 84 | TLV_OBJECTS[TLV_utils.context_FCP] = { 85 | 0x84: (Card.decode_df_name, "DF name"), 86 | } 87 | TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] 88 | 89 | def __init__(self, reader): 90 | Card.__init__(self, reader) 91 | self.last_sw = None 92 | self.sw_changed = False 93 | 94 | def post_merge(self): 95 | ## Called after cards.__init__.Cardmultiplexer._merge_attributes 96 | self.TLV_OBJECTS[TLV_utils.context_FCP][0x84] = (self._decode_df_name, "DF name") 97 | self.TLV_OBJECTS[TLV_utils.context_FCI][0x84] = (self._decode_df_name, "DF name") 98 | 99 | def decode_statusword(self): 100 | if self.last_sw is None: 101 | return "No command executed so far" 102 | else: 103 | retval = None 104 | 105 | matched_sw = self.match_statusword(self.STATUS_WORDS.keys(), self.last_sw) 106 | if matched_sw is not None: 107 | retval = self.STATUS_WORDS.get(matched_sw) 108 | if isinstance(retval, str): 109 | retval = retval % { "SW1": ord(self.last_sw[0]), 110 | "SW2": ord(self.last_sw[1]) } 111 | 112 | elif callable(retval): 113 | retval = retval( ord(self.last_sw[0]), 114 | ord(self.last_sw[1]) ) 115 | 116 | if retval is None: 117 | return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw) 118 | else: 119 | return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw)) 120 | 121 | def _real_send(self, apdu): 122 | result = Card._real_send(self, apdu) 123 | 124 | self.last_sw = result.sw 125 | self.sw_changed = True 126 | 127 | return result 128 | 129 | def _send_with_retry(self, apdu): 130 | result = self._real_send(apdu) 131 | 132 | if self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE): 133 | ## Need to call GetResponse 134 | gr_apdu = C_APDU(self.COMMAND_GET_RESPONSE, le = result.sw2, cla=apdu.cla) # FIXME 135 | result = R_APDU(self._real_send(gr_apdu)) 136 | elif self.check_sw(result.sw, self.PURPOSE_RETRY) and apdu.Le == 0: 137 | ## Retry with correct Le 138 | gr_apdu = C_APDU(apdu, le = result.sw2) 139 | result = R_APDU(self._real_send(gr_apdu)) 140 | 141 | return result 142 | 143 | 144 | 145 | def verify_pin(self, pin_number, pin_value): 146 | apdu = C_APDU(self.APDU_VERIFY_PIN, P2 = pin_number, 147 | data = pin_value) 148 | result = self.send_apdu(apdu) 149 | return self.check_sw(result.sw) 150 | 151 | def cmd_verify(self, pin_number, pin_value): 152 | """Verify a PIN.""" 153 | pin_number = int(pin_number, 0) 154 | pin_value = binascii.a2b_hex("".join(pin_value.split())) 155 | self.verify_pin(pin_number, pin_value) 156 | 157 | COMMANDS = { 158 | "verify": cmd_verify, 159 | } 160 | 161 | -------------------------------------------------------------------------------- /cards/java_card.py: -------------------------------------------------------------------------------- 1 | import utils, binascii 2 | from iso_card import * 3 | from utils import C_APDU 4 | 5 | class Java_Card(ISO_Card): 6 | DRIVER_NAME = ["Generic Java"] 7 | APPLICATIONS = { 8 | "\xa0\x00\x00\x00\x01\x01": ("muscle", "MUSCLE applet") 9 | } 10 | 11 | -------------------------------------------------------------------------------- /cards/mtcos_card.py: -------------------------------------------------------------------------------- 1 | import utils, TLV_utils 2 | from iso_7816_4_card import * 3 | from rfid_card import RFID_Card 4 | import building_blocks 5 | 6 | class MTCOS_Card(ISO_7816_4_Card,building_blocks.Card_with_80_aa): 7 | DRIVER_NAME = ["MTCOS"] 8 | 9 | ATRS = [ 10 | # This is the correct ATR according to PC/SC v.2 part 3 11 | ("3b8980014d54434f53730102013f", None), 12 | # This is what SCM SCR331/SDI010 shows 13 | ("3BFE9100FF918171FE40004120001177B1024D54434F537301CF", None), 14 | ] 15 | 16 | STOP_ATRS = [ 17 | # Don't use this class for the contactless interface, but instead MTCOS_Card_RFID below 18 | ("3b8980014d54434f53730102013f", None), 19 | ] 20 | 21 | COMMANDS = { 22 | "list_dirs": building_blocks.Card_with_80_aa.cmd_listdirs, 23 | "list_files": building_blocks.Card_with_80_aa.cmd_listfiles, 24 | "ls": building_blocks.Card_with_80_aa.cmd_list, 25 | } 26 | 27 | def decode_auth_scheme(value): 28 | return " (0x%02x) " % ord(value) + { 29 | 0x1: "MaskTech scheme", 30 | 0x2: "NETLINK compatible", 31 | 0x4: "ICAO - basic access control", 32 | }.get(ord(value), "RFU") 33 | 34 | reset_retry_counter_byte_descriptions = ( 35 | (0xFF, 0x00, None, "Retry counter is unused"), 36 | (0x80, 0x00, None, "Retry counter is reset upon successful both Authentication and RESET RETRY COUNTER"), 37 | (0x80, 0x80, None, "Retry counter can only be reset using RESET RETRY COUNTER"), 38 | ) 39 | def decode_retry_counter(value): 40 | results = [" %s" % utils.hexdump(value, short=True)] 41 | results.append("Number of further allowed attempts: %i" % ord(value[0])) 42 | results.append("New value of the retry counter: %i\n\t%s" % ( 43 | ord(value[1]) % 0x7F, 44 | "\n\t".join( utils.parse_binary( 45 | ord(value[1]), MTCOS_Card.reset_retry_counter_byte_descriptions, True 46 | ) ) 47 | ) ) 48 | return "\n".join(results) 49 | 50 | application_class_byte_descriptions = ( 51 | (0x80, 0x80, None, "Secret file"), 52 | (0xC0, 0x80, None, "RFU"), 53 | (0xC0, 0xC0, None, "Keyfile"), 54 | (0xC8, 0xC8, None, "Possible application area: Signature"), 55 | (0xC4, 0xC4, None, "Possible application area: Encryption"), 56 | (0xC2, 0xC2, None, "Possible application area: Cryptographic checksum (Secure Messaging)"), 57 | (0xC1, 0xC1, None, "Possible application area: Authentication"), 58 | ) 59 | cryptographic_algorithm_byte_descriptions = ( 60 | (0x80, 0x00, None, "Symmetric Algorithm"), 61 | (0x8F, 0x08, None, "DES-Key"), 62 | (0x8E, 0x0C, None, "3DES-Key (Triple DES with 2 or 3 keys)"), 63 | (0x81, 0x00, None, " - ECB"), 64 | (0x81, 0x01, None, " - CBC"), 65 | ) 66 | cryptographic_algorithm_byte_descriptions_old = ( 67 | (0x80, 0x80, None, "Asymmetric Algorithm"), 68 | (0xC0, 0x80, None, "Private Key"), 69 | (0xB0, 0x80, None, "RSA"), 70 | (0xB1, 0x81, None, " - Raw"), 71 | (0xB2, 0x82, None, " - PKCS#1 type 2 and 2"), 72 | (0xB4, 0x84, None, " - ISO/IEC 9796-2"), 73 | ) 74 | cryptographic_algorithm_byte_descriptions_new = ( 75 | (0x80, 0x00, None, "Last Byte"), 76 | (0x80, 0x80, None, "At least one byte following"), 77 | ) 78 | ## FIXME: This is broken, the MTCOS guys changed their bitflags, no way to distinguish old from new 79 | def decode_83(value): 80 | ## 0x83 in 0xA5 is either "Cryptographic algorithm and allowed applications" or 81 | ## "Default key reference for authentication commands in this environment" 82 | 83 | if len(value) >= 2: 84 | results = [" %s" % utils.hexdump(value, short=True)] 85 | results.append("Application class: 0x%02x\n\t%s" % ( 86 | ord(value[0]), 87 | "\n\t".join( utils.parse_binary( 88 | ord(value[0]), MTCOS_Card.application_class_byte_descriptions, True 89 | ) ) 90 | ) ) 91 | results.append("Cryptographic algorithm: 0x%02x\n\t%s" % ( 92 | ord(value[1]), 93 | "\n\t".join( utils.parse_binary( 94 | ord(value[1]), MTCOS_Card.cryptographic_algorithm_byte_descriptions, True 95 | ) ) 96 | ) ) 97 | return "\n".join(results) 98 | elif len(value) == 1: 99 | return "\nDefault key reference for authentication commands in this environment: 0x%02x" % ord(value[0]) 100 | 101 | def decode_security_attributes(value): 102 | results = [] 103 | if len(value) == 6: 104 | results.append( " " + utils.hexdump(value, short=True) ) 105 | else: 106 | results.append("") 107 | 108 | for i in range(len(value)/6): 109 | part = value[i*6:i*6+6] 110 | partresponse = [] 111 | if len(value) != 6: 112 | partresponse.append("Rule: %s\n" % utils.hexdump(part, short=True)) 113 | 114 | if ord(part[0])&0xFE == 0x60: 115 | partresponse.append("Admin commands") 116 | else: 117 | partresponse.append("Command 0x%02X" % (ord(part[0])&0xFE) ) 118 | all = not (ord(part[0])&0x01) 119 | 120 | secrets = [] 121 | b2 = ord(part[1]) 122 | for k in range(4): 123 | if b2 & (0x10< 1: 140 | partresponse.append( 141 | " needs\n\t " + (all and "\n\tAND " or "\n\t OR ").join(secrets) 142 | ) 143 | elif len(secrets) == 1: 144 | partresponse.append(" needs " + secrets[0]) 145 | elif len(secrets) == 0: 146 | partresponse.append(" is always allowed") 147 | 148 | def decode_key(value): 149 | partresponse.append( (value&0x80) and "local" or "global" ) 150 | partresponse.append(" key, ") 151 | partresponse.append( (value&0x40) and "random" or "any" ) 152 | partresponse.append(" IV") 153 | if not (value & 0x20): 154 | partresponse.append(", key number: ") 155 | if (value & 0x1F) != 0x1F: 156 | partresponse.append("0x%02x" % (value & 0x1F) ) 157 | else: 158 | partresponse.append("RFU") 159 | 160 | b5 = ord(part[4]) 161 | b6 = ord(part[5]) 162 | if b5 == 0xff and b6 == 0xff and len(secrets) <= 1: 163 | partresponse.append(", No secure messaging required") 164 | else: 165 | if b5 == 0xff: 166 | partresponse.append("\nSecure messaging: no checksum required") 167 | else: 168 | partresponse.append("\nCryptographic checksum with ") 169 | decode_key(b5) 170 | 171 | if b6 == 0xff: 172 | partresponse.append("\nSecure messaging: no encryption required") 173 | elif not (b6 & 0x20): 174 | partresponse.append("\nEncryption with ") 175 | decode_key(b6) 176 | else: 177 | partresponse.append("\nEncryption: RFU") 178 | 179 | if len(value) != 6: 180 | results.append("\n\t".join("".join(partresponse).splitlines())) 181 | else: 182 | results.append("".join(partresponse)) 183 | 184 | return "\n".join(results) 185 | 186 | physical_access_byte_descriptions = ( 187 | (0xFF, 0x01, None, "Access by contacts according to ISO 7816-3"), 188 | (0xFF, 0x02, None, "Access by contactless (radio frequency) according to ISO 14443"), 189 | (0xFF, 0x03, None, "Dual interface"), 190 | (0xFC, 0x00, "RFU", None), 191 | ) 192 | def decode_physical_access(value): 193 | return "\n"+"\n".join( 194 | utils.parse_binary( 195 | ord(value[0]), MTCOS_Card.physical_access_byte_descriptions, True 196 | ) 197 | ) 198 | 199 | TLV_utils.identifier("context_A5") 200 | TLV_OBJECTS = { 201 | TLV_utils.context_FCP: { 202 | 0x86: (decode_security_attributes, "Security attributes"), 203 | 0x91: (decode_physical_access, "Following security attribute is valid for"), 204 | 0xA1: (TLV_utils.recurse, "Security attribute template for physical access", TLV_utils.context_FCP), 205 | 0xA5: (TLV_utils.recurse, "Proprietary security attributes", context_A5), 206 | }, 207 | context_A5: { 208 | 0x81: (decode_auth_scheme, "Authentication scheme"), 209 | 0x82: (decode_retry_counter, "Retry counter"), 210 | 0x83: (decode_83, "Cryptographic algorithm and allowed applications OR Default key reference"), 211 | } 212 | } 213 | TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] 214 | 215 | class MTCOS_Card_RFID(MTCOS_Card,RFID_Card): 216 | DRIVER_NAME = ["MTCOS RFID"] 217 | ATRS = [ 218 | ("3b8980014d54434f53730102013f", None), 219 | ] 220 | RFID_Card.STOP_ATRS.append( ("3b8980014d54434f53730102013f", None) ) 221 | 222 | STOP_ATRS = [] 223 | 224 | COMMANDS = {} 225 | COMMANDS.update(MTCOS_Card.COMMANDS) 226 | COMMANDS.update(RFID_Card.COMMANDS) 227 | -------------------------------------------------------------------------------- /cards/nfc_application.py: -------------------------------------------------------------------------------- 1 | from generic_application import Application 2 | import struct, binascii, os, datetime, sys, utils 3 | 4 | 5 | class NFC_Application(Application): 6 | DRIVER_NAME = ["NFC Type 4"] 7 | SELECT_FILE_P1 = 0 8 | 9 | AID_LIST = [ 10 | "d2760000850100" 11 | ] 12 | 13 | def cmd_parse_cc(self): 14 | "Read and parse the CC (Capability Container) EF" 15 | result = self.open_file("\xe1\x03") 16 | if self.check_sw(result.sw): 17 | contents, sw = self.read_binary_file() 18 | if len(contents) > 0: 19 | print utils.hexdump(contents,linelen=self.HEXDUMP_LINELEN) 20 | 21 | if len(contents) < 0xf: 22 | print "Invalid CC EF, can't parse (too short: 0x%x bytes)" % len(contents) 23 | else: 24 | cclen, version, MLe, MLc, ndef_control_tlv = struct.unpack('>HBHH8s', contents[:0xf]) 25 | 26 | print " CC length: %i (0x%x)%s" % (cclen, cclen, cclen == 0xffff and ", RFU" or "") 27 | print "Mapping version: %i.%i" % (version >> 4, version & 0xf) 28 | print " Maximum Le: %i (0x%x)%s" % (MLe, MLe, MLe <= 0xe and ", RFU" or "") 29 | print " Maximum Lc: %i (0x%x)%s" % (MLc, MLc, MLc == 0x0 and ", RFU" or "") 30 | 31 | print "NDEF File Control TLV: %s" % utils.hexdump(ndef_control_tlv, short=True) 32 | if len(contents) > 0xf: 33 | print "More TLV blocks: %s" % utils.hexdump(contents[0xf:], short=True) 34 | 35 | COMMANDS = { 36 | "parse_cc": cmd_parse_cc, 37 | } 38 | -------------------------------------------------------------------------------- /cards/pn532_card.py: -------------------------------------------------------------------------------- 1 | import utils, binascii 2 | from iso_card import * 3 | 4 | class PN532_Virtual_Card(ISO_Card): 5 | # This is a virtual card that is enabled for the ACS ACR reader that 6 | # contains a PN532 module 7 | DRIVER_NAME = ["PN532"] 8 | 9 | STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) 10 | COMMANDS = dict(ISO_Card.COMMANDS) 11 | 12 | APDU_TRANSCEIVE_PN532 = C_APDU(cla=0xff, ins=0, p1=0, p2=0) 13 | 14 | # The ACR122 has a maximum response data size of 0xf8 15 | APDU_READ_BINARY = C_APDU(cla=0, ins=0xb0, le=0xf8) 16 | 17 | def cmd_pn532(self, *cmd): 18 | "Transmit a command to the PN532 and receive the response" 19 | result = self.pn532_transceive(binascii.unhexlify("".join("".join(cmd).split()))) 20 | print utils.hexdump(result.data) 21 | 22 | parsed = self.pn532_parse(result.data) 23 | if len(parsed) > 0: 24 | print "\n".join(parsed) + "\n" 25 | 26 | def cmd_pn532_poll(self): 27 | "Poll for cards in the field" 28 | self.cmd_pn532("d4 4a 01 00") 29 | 30 | def pn532_transceive(self, cmd): 31 | if len(cmd) > 1: 32 | if cmd[0] == "\xd4": 33 | s = "pn532_prepare_command_%02X" % ord(cmd[1]) 34 | if hasattr(self, s): 35 | cmd = getattr(self, s)(cmd) 36 | 37 | if hasattr(self.reader, "pn532_transceive_raw"): 38 | return R_APDU(self.reader.pn532_transceive_raw(cmd)) 39 | else: 40 | apdu = C_APDU(self.APDU_TRANSCEIVE_PN532, lc=len(cmd), data=cmd) 41 | response = self.send_apdu(apdu) 42 | return response 43 | 44 | def pn532_parse(self, response): 45 | result = [] 46 | type = None 47 | cmd = None 48 | 49 | if len(response) == 0: 50 | pass 51 | elif response[0] == "\xd4": 52 | type="command" 53 | elif response[0] == "\xd5": 54 | type="response" 55 | else: 56 | result.append("Invalid PN532 direction header") 57 | 58 | if len(response) > 1: cmd = ord(response[1]) 59 | 60 | if type is not None: 61 | desc = "PN532 %s (%s)" % (type, 62 | self.PN532_COMMANDS.get(cmd & 0xfe, "Unknown command") ) 63 | result.append(desc) 64 | 65 | if len(response) > 1: 66 | s = "pn532_parse_%s_%02X" % (type, cmd) 67 | if hasattr(self, s): 68 | result.extend( getattr(self, s)(response[2:]) ) 69 | elif len(response) > 2: 70 | result.append( "No detailed decoding available" ) 71 | 72 | return result 73 | 74 | def pn532_parse_response_03(self, response): 75 | return [ "Version: PN5%02X, firmware %i.%i (cap: %02X)" % tuple(map(ord, response)) ] 76 | 77 | def pn532_parse_response_05(self, response): 78 | result = ["Last error: %02X" % ord(response[0]), 79 | "External field: %s" % ( response[1] == "\x01" and "present" or "not present" ), 80 | "Number of targets: %i" % ord(response[2])] 81 | 82 | for i in range( (len(response)-4)/4 ): 83 | t = "Target %i: %i kbps receive, %i kbps send, type: %s" % ( 84 | ord(response[3+i*4]), 85 | self.PN532_BIT_RATES.get( ord(response[3+i*4+1]), "XXX" ), 86 | self.PN532_BIT_RATES.get( ord(response[3+i*4+2]), "XXX" ), 87 | self.PN532_TAG_TYPES.get( ord(response[3+i*4+3]), "unknown" ), 88 | ) 89 | result.append(t) 90 | 91 | result.append( "SAM status: %02X" % ord(response[-1]) ) 92 | return result 93 | 94 | def pn532_prepare_command_4A(self, cmd): 95 | if len(cmd) > 3: 96 | self._last_baudrate_polled = ord(cmd[3]) 97 | else: 98 | self._last_baudrate_polled = None 99 | return cmd 100 | 101 | def pn532_parse_response_4B(self, response): 102 | r = utils.PN532_Response_InListPassiveTarget(data = response) 103 | parse_ok = r.parse_result(self._last_baudrate_polled) 104 | 105 | result = ["Targets detected: %i" % len(r.targets)] 106 | 107 | if not parse_ok: 108 | result.append("Parse error, results unreliable") 109 | 110 | for index, target in r.targets.items(): 111 | s = "Target %i: %s" % (index, target.type) 112 | if target.type == utils.PN532_Target.TYPE_ISO14443A: 113 | s = s + ", SENS_RES: %02X %02X, SEL_RES: %02X" % ( 114 | target.sens_res[0], target.sens_res[1], target.sel_res) 115 | if len(target.nfcid) > 0: 116 | s = s + ", NFCID (%i bytes): %s" % ( 117 | len(target.nfcid), " ".join(map(lambda a: "%02X" % a, target.nfcid))) 118 | if len(target.ats) > 0: 119 | s = s + ", ATS (%i bytes): %s" % ( 120 | len(target.ats), " ".join(map(lambda a: "%02X" % a, target.ats))) 121 | 122 | result.append(s) 123 | elif target.type == utils.PN532_Target.TYPE_ISO14443B: 124 | s = s + ", ATQB: %s" % ( 125 | " ".join(map(lambda a: "%02X" % a, target.atqb)) ) 126 | 127 | if len(target.attrib_res) > 0: 128 | s = s + ", ATTRIB_RES (%i bytes): %s" % ( 129 | len(target.attrib_res), " ".join(map(lambda a: "%02X" % a, target.attrib_res))) 130 | 131 | result.append(s) 132 | 133 | return result 134 | 135 | def can_handle(cls, reader): 136 | """Determine whether this class can handle a given reader object.""" 137 | if reader.name.startswith("ACS ACR 38U-CCID"): 138 | return True 139 | return False 140 | 141 | can_handle = classmethod(can_handle) 142 | 143 | 144 | STATUS_WORDS.update( { 145 | '\x63\x00': "Operation failed", 146 | '\x63\x01': "PN532 did not respond", 147 | '\x63\x27': "PN532 response checksum wrong", 148 | '\x63\x7f': "PN532 command wrong", 149 | } ) 150 | 151 | COMMANDS.update( { 152 | "pn532": cmd_pn532, 153 | "pn532_poll": cmd_pn532_poll, 154 | } ) 155 | 156 | PN532_COMMANDS = { 157 | 0x00: "Diagnose", 158 | 0x02: "GetFirmwareVersion", 159 | 0x04: "GetGeneralStatus", 160 | 0x06: "ReadRegister", 161 | 0x08: "WriteRegister", 162 | 0x0c: "ReadGPIO", 163 | 0x0e: "WriteGPIO", 164 | 0x10: "SetSerialBaudrate", 165 | 0x12: "SetParameters", 166 | 0x14: "SAMConfiguration", 167 | 0x16: "PowerDown", 168 | 0x32: "RFConfiguration", 169 | 0x58: "RFRegulationTest", 170 | 0x56: "InJumpForDEP", 171 | 0x46: "InJumpForPSL", 172 | 0x4A: "InListPassiveTarget", 173 | 0x50: "InATR", 174 | 0x4E: "InPSL", 175 | 0x40: "InDataExchange", 176 | 0x42: "InCommunicateThru", 177 | 0x44: "InDeselect", 178 | 0x52: "InRelease", 179 | 0x54: "InSelect", 180 | 0x60: "InPoll", 181 | 0x8C: "TgInitAsTarget", 182 | 0x92: "TgSetGeneralBytes", 183 | 0x86: "TgGetData", 184 | 0x8E: "TgSetData", 185 | 0x94: "TgSetMetaData", 186 | 0x88: "TgGetInitiatorCommand", 187 | 0x90: "TgResponseToInitiator", 188 | 0x8A: "TgGetTargetStatus", 189 | } 190 | 191 | PN532_BIT_RATES = { 192 | 0x0: 106, 193 | 0x1: 212, 194 | 0x2: 424, 195 | } 196 | 197 | PN532_TAG_TYPES = { 198 | 0x00: "Mifare, ISO 14443-3 A/B or ISO 18092 passive 106 kbps", 199 | 0x10: "FeliCa or ISO 18092 passive 212/424 kbps", 200 | 0x01: "ISO 18092 active", 201 | 0x02: "Innovision Jewel", 202 | } 203 | -------------------------------------------------------------------------------- /cards/postcard_card.py: -------------------------------------------------------------------------------- 1 | import utils, re, sys, getpass, struct 2 | from iso_7816_4_card import * 3 | 4 | class Postcard_Card(ISO_7816_4_Card): 5 | DRIVER_NAME = ["Postcard"] 6 | CLA = 0xbc 7 | COMMAND_GET_RESPONSE = C_APDU(cla=CLA,ins=0xc0) 8 | 9 | APDU_SELECT_FILE = C_APDU(cla=CLA,ins=0xa4) 10 | APDU_READ_BINARY = C_APDU(cla=CLA,ins=0xb0,le=0x80) 11 | 12 | STATUS_MAP = { 13 | Card.PURPOSE_SUCCESS: ("90[124]0", ) 14 | } 15 | 16 | ATRS = [ 17 | ("3f65351002046c90..", None), 18 | ("3f65356402046c9040", None), 19 | ] 20 | 21 | def _get_binary(self, offset, length): 22 | command = C_APDU(self.APDU_READ_BINARY, p1 = offset >> 8, p2 = offset & 0xff, le = length) 23 | result = self.send_apdu(command) 24 | assert self.check_sw(result.sw) 25 | 26 | return result.data 27 | 28 | def _get_address(self, definition_data, offset): 29 | return ((ord(definition_data[offset]) * 256 + ord(definition_data[offset+1])) >> 5) << 3 30 | 31 | ZONE_ADDRESSES = [ 32 | ("adl", 0x09C8), 33 | ("adt", 0x09CC), 34 | ("adc", 0x09D0), 35 | ("adm", 0x09D4), 36 | ("ad2", 0x09D8), 37 | ("ads", 0x09DC), 38 | ("ad1", 0x09E8), 39 | ] 40 | def cmd_calculate_zone_addresses(self): 41 | "Read the zone definitions and calculate the zone addresses" 42 | READSTART = 0x09c0 43 | definition_data = self._get_binary(READSTART, length=0x30) 44 | 45 | result = {} 46 | for name, offset in self.ZONE_ADDRESSES: 47 | result[name] = self._get_address(definition_data, (offset - READSTART)>>1) 48 | 49 | for name,_ in self.ZONE_ADDRESSES: 50 | print "%s: %04x" % (name, result[name]) 51 | 52 | def cmd_read_identification_zone(self): 53 | "Read the identification zone" 54 | data = self._get_binary(0x0948, length=0x60) 55 | 56 | chaine_initiale = binascii.b2a_hex(data[4:]) 57 | print "chaine initiale", chaine_initiale 58 | #suppression_des_3 = re.sub(r'x', '3', re.sub(r'3', '', re.sub(r'33', "x", chaine_initiale) ) ) 59 | #suppression_des_3 = "".join(chaine_initiale.split("3")) + "0" 60 | suppression_des_3 = [] 61 | still_there = True 62 | for index,char in enumerate(chaine_initiale): 63 | if still_there and index % 8 == 0: 64 | if char == "3": 65 | continue 66 | else: 67 | still_there = False 68 | suppression_des_3.append(char) 69 | suppression_des_3 = "".join(suppression_des_3) 70 | 71 | print "suppression des 3", suppression_des_3 72 | new_data = binascii.a2b_hex(suppression_des_3) 73 | print utils.hexdump(new_data) 74 | 75 | fields = [ 76 | (None, 2), 77 | ("card number", 19), 78 | ("usage code", 3), 79 | ("valid from", 4), 80 | ("language code", 3), 81 | ("valid till", 4), 82 | ("currency code", 3), 83 | ("denomination", 1), 84 | ("(unknown)", 3), 85 | ("card holder", 26*2) 86 | ] 87 | 88 | print "Decoding:" 89 | pos = 0 90 | for name, length in fields: 91 | value = suppression_des_3[pos:pos+length] 92 | pos = pos+length 93 | if name is None: 94 | continue 95 | print "\t%20s: %s" % (name, value) 96 | 97 | print "\t%20s '%s'" % ("", binascii.a2b_hex(value)) 98 | 99 | def cmd_give_pin(self): 100 | "Enter a pin" 101 | old = sys.modules["cards.generic_card"].DEBUG 102 | try: 103 | pin = getpass.getpass("Enter PIN: ") 104 | if len(pin) != 4: 105 | raise ValueError, "PIN must be 4 characters in length" 106 | pinint = int(pin, 16) 107 | pinint = (pinint << 14) | 0x3fff 108 | data = struct.pack(">I", pinint) 109 | sys.modules["cards.generic_card"].DEBUG = False 110 | command = C_APDU(cla=0xBC, ins=0x20, data=data, le=0) 111 | result = self.send_apdu(command) 112 | finally: 113 | sys.modules["cards.generic_card"].DEBUG = old 114 | 115 | 116 | COMMANDS = { 117 | "calculate_zone_addresses": cmd_calculate_zone_addresses, 118 | "read_identification_zone": cmd_read_identification_zone, 119 | "give_pin": cmd_give_pin, 120 | } 121 | -------------------------------------------------------------------------------- /cards/rfid_card.py: -------------------------------------------------------------------------------- 1 | import utils 2 | from iso_card import * 3 | import building_blocks 4 | 5 | class RFID_Card(ISO_Card): 6 | DRIVER_NAME = ["RFID"] 7 | APDU_GET_UID = utils.C_APDU(CLA=0xff, INS=0xCA, p1=0, p2=0, Le=0) 8 | 9 | ATRS = [ 10 | # Contactless storage cards 11 | ("3b8f8001804f0ca000000306......00000000..", None), 12 | # All other cards that follow the general ATR format 13 | ("3b8.8001.*", None), 14 | ] 15 | 16 | STOP_ATRS = [ 17 | # Mifare, handled below 18 | ("3b8f8001804f0ca000000306..000[1-3]00000000..", None), 19 | ("3B8180018080", None), 20 | ] 21 | 22 | def get_uid(self): 23 | result = self.send_apdu(utils.C_APDU(self.APDU_GET_UID)) 24 | return result.data 25 | 26 | def cmd_get_uid(self): 27 | "Get the UID or SNR or PUPI of the currently connected card." 28 | uid = self.get_uid() 29 | print utils.hexdump(uid, short=True) 30 | 31 | COMMANDS = { 32 | "get_uid": cmd_get_uid, 33 | } 34 | 35 | STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) 36 | STATUS_WORDS.update( { 37 | "\x62\x82": "End of file (or UID) reached before Le bytes", 38 | "\x67\x00": "Wrong Length", 39 | "\x68\x00": "Class byte is not correct", 40 | "\x6a\x81": "Function not supported", 41 | "\x6b\x00": "Wrong parameters P1-P2", 42 | } ) 43 | 44 | class RFID_Storage_Card(building_blocks.Card_with_read_binary,RFID_Card): 45 | STOP_ATRS = [] 46 | ATRS = [] 47 | STATUS_MAP = dict(RFID_Card.STATUS_MAP) 48 | STATUS_MAP.update( { 49 | Card.PURPOSE_RETRY: ("6C??", ), 50 | } ) 51 | 52 | APDU_READ_BINARY = utils.C_APDU(CLA=0xff, INS=0xb0, Le=0) 53 | COMMANDS = dict(building_blocks.Card_with_read_binary.COMMANDS) 54 | COMMANDS.update(RFID_Card.COMMANDS) 55 | 56 | class Mifare_Card(RFID_Storage_Card): 57 | DATA_UNIT_SIZE=4 58 | 59 | class Mifare_Classic_Card(Mifare_Card): 60 | pass 61 | 62 | class Mifare_Classic_1k_Card(Mifare_Classic_Card): 63 | DRIVER_NAME = ["Mifare Classic 1k"] 64 | 65 | ATRS = [ 66 | # Classic 1k 67 | ("3b8f8001804f0ca000000306..000100000000..", None), 68 | ] 69 | class Mifare_Classic_4k_Card(Mifare_Classic_Card): 70 | DRIVER_NAME = ["Mifare Classic 4k"] 71 | 72 | ATRS = [ 73 | # Classic 4k 74 | ("3b8f8001804f0ca000000306..000200000000..", None), 75 | ] 76 | 77 | class Mifare_Ultralight_Card(Mifare_Card): 78 | DRIVER_NAME = ["Mifare Ultralight"] 79 | HEXDUMP_LINELEN = 4 80 | 81 | ATRS = [ 82 | # Ultralight 83 | ("3b8f8001804f0ca000000306..000300000000..", None), 84 | ] 85 | 86 | class Mifare_DESfire_Card(RFID_Card): 87 | DRIVER_NAME = ["Mifare DESfire"] 88 | ATRS = [ 89 | ("3B8180018080", None) 90 | ] 91 | STOP_ATRS = [] 92 | 93 | STATUS_WORDS = { 94 | "\x91\x00": "Successful Operation", 95 | "\x91\x0C": "No changes done to backup files, CommitTransaction/AbortTransaction not necessary", 96 | "\x91\x0E": "Insufficient NV-Memory to complete command", 97 | "\x91\x1C": "Command code not supported", 98 | "\x91\x1E": "CRC or MAC does not match data. Padding bytes not valid", 99 | "\x91\x40": "Invalid key number specified", 100 | "\x91\x7E": "Length of command string invalid", 101 | "\x91\x9D": "Current configuration / status does not allow the requested command", 102 | "\x91\x9E": "Value of the parameter(s) invalid", 103 | "\x91\xA0": "Requested AID not present on PICC", 104 | "\x91\xA1": "Unrecoverable error within application, application will be disabled", 105 | "\x91\xAE": "Current authentication status does not allow the requested command", 106 | "\x91\xAF": "Additional data frame is expected to be sent", 107 | "\x91\xBE": "Attempt to read/write data from/to beyond the file's/record's limits. Attempt to exceed the limits of a value file.", 108 | "\x91\xC1": "Unrecoverable error within PICC, PICC will be disabled", 109 | "\x91\xCA": "Previous Command was not fully completed. Not all Frames were requested or provided by the PCD", 110 | "\x91\xCD": "PICC was disabled by an unrecoverable error", 111 | "\x91\xCE": "Number of Applications limited to 28, no additional CreateApplication possible", 112 | "\x91\xDE": "Creation of file/application failed because file/application with same number already exists", 113 | "\x91\xEE": "Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated", 114 | "\x91\xF0": "Specified file number does not exist", 115 | "\x91\xF1": "Unrecoverable error within file, file will be disabled", 116 | } 117 | 118 | DEFAULT_CLA = 0x90 119 | 120 | def wrap_native(self, native_command): 121 | print repr(native_command) 122 | if len(native_command) > 1: 123 | apdu = utils.C_APDU(cla=self.DEFAULT_CLA, ins=native_command[0], data=native_command[1:], le=0) 124 | elif len(native_command) == 1: 125 | apdu = utils.C_APDU(cla=self.DEFAULT_CLA, ins=native_command[0], le=0) 126 | else: 127 | raise ValueError, "len(native_command) must be >= 1" 128 | 129 | result = self.send_apdu(apdu) 130 | 131 | return result.data, result.sw2 132 | 133 | def cmd_wrap_native(self, *args): 134 | "Wrap a native DESfire command into an ISO 7816 APDU" 135 | data, returncode = self.wrap_native( binascii.a2b_hex( "".join("".join(args).split()) ) ) 136 | print utils.hexdump(data) 137 | 138 | COMMANDS = dict(RFID_Card.COMMANDS) 139 | COMMANDS.update({ 140 | "wrap": cmd_wrap_native, 141 | }) 142 | -------------------------------------------------------------------------------- /cards/seccos_card.py: -------------------------------------------------------------------------------- 1 | import utils 2 | from iso_7816_4_card import * 3 | 4 | class SECCOS_Card(ISO_7816_4_Card): 5 | DRIVER_NAME = ["SECCOS"] 6 | SELECT_P2 = 0x04 7 | 8 | ATRS = [ 9 | ("3BFF1800FF8131FE4565630D07630764000D........0615..", None), 10 | ("3BFF1800FF8131FE4565630D08650764000D........0616..", None), 11 | ("3BFF1800FF8131FE45656311064002500010........0500..", None), 12 | ("3BFF1800FF8131FE45656311066202800011........0613..", None), 13 | ("3BFF1800FF8131FE45656311076402800011........0619..", None), 14 | ("3BFF1800FF8131FE45656311084302500010........0530..", None), 15 | ("3BFF1800FF8131FE45656311086602800011........0620..", None), 16 | ("3BFF9600FF8131FE4565631901500280000F........5012..", None), 17 | ("3BEF00FF8131FE45656311086602800011284004070620BC", None), 18 | ("3bff1800ff8131fe4565630d08680764000d91088000062128", None), 19 | 20 | ("3b8780018031807396128040", None), # T=CL 21 | ] 22 | 23 | APPLICATIONS = { 24 | "\x52\x4F\x4F\x54": ("MF", "Master File ZKA-Chipkarte"), 25 | } 26 | 27 | def decode_sfi_path(value): 28 | return " SFI: 0x%02x, path: %s" % (ord(value[0]) >> 3, utils.hexdump(value[1:], short=True)) 29 | 30 | TLV_OBJECTS = { 31 | TLV_utils.context_FMD: { 32 | 0x85: (decode_sfi_path, "SFI with path"), 33 | }, 34 | } 35 | TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FMD] 36 | -------------------------------------------------------------------------------- /cards/starcos_card.py: -------------------------------------------------------------------------------- 1 | import utils 2 | from iso_7816_4_card import * 3 | 4 | class Starcos_Card(ISO_7816_4_Card): 5 | DRIVER_NAME = ["Starcos"] 6 | APDU_READ_BINARY = C_APDU(ins=0xb0,le=0xfe) 7 | 8 | def change_dir(self, fid = None): 9 | "Change to a child DF. Alternatively, change to MF if fid is None." 10 | if fid is None: 11 | return self.select_file(0x00, 0x0C, self.FID_MF) 12 | else: 13 | return self.select_file(0x00, 0x0C, fid) 14 | 15 | ATRS = list(ISO_Card.ATRS) 16 | ATRS.extend( [ 17 | ("3bb794008131fe6553504b32339000d1", None), 18 | ] ) 19 | 20 | -------------------------------------------------------------------------------- /cards/vrs_application.py: -------------------------------------------------------------------------------- 1 | from generic_application import Application 2 | import struct, binascii, os, datetime, sys, time 3 | from iso_7816_4_card import ISO_7816_4_Card 4 | import utils, TLV_utils, generic_card 5 | 6 | 7 | class VRS_Application(Application): 8 | DRIVER_NAME = ["VRS"] 9 | 10 | AID_LIST = [ 11 | "d2760000254b414e4d303100", 12 | "d2760001354b414e4d303100", 13 | ] 14 | 15 | class VrsTicket(object): 16 | def __init__(self): 17 | self._birthdate = None 18 | self._maindata = [] 19 | self._mainblob = None 20 | self._rawdata = None 21 | self._tlvdata = None 22 | self._card = None 23 | 24 | def from_card(cls, card, record_no = 1): 25 | if not isinstance(card, VRS_Application): 26 | if not isinstance(card, ISO_7816_4_Card): 27 | raise ValueError, "card must be a VRS_Application object or a ISO_7816_4_Card object, not %s" % type(card) 28 | else: 29 | result = card.select_application(binascii.a2b_hex(VRS_Application.AID_LIST[0])) 30 | if not card.check_sw(result.sw): 31 | raise EnvironmentError, "card did not accept SELECT APPLICATION, sw was %02x %02x" % (result.sw1, result.sw2) 32 | assert isinstance(card, VRS_Application) 33 | 34 | c = cls() 35 | c._card = card 36 | 37 | result = card.open_file("\x0c\x05") 38 | if card.check_sw(result.sw): 39 | contents = card.read_record(record_no, 4) 40 | if len(contents) > 0: 41 | c._parse( contents ) 42 | else: 43 | raise KeyError, "No ticket in record no. %i" % record_no 44 | else: 45 | raise EnvironmentError, "card did not accept SELECT FILE, sw was %02x %02x" % (result.sw1, result.sw2) 46 | 47 | return c 48 | 49 | def _parse(self, contents): 50 | self._rawdata = contents 51 | self._tlvdata = TLV_utils.unpack(contents) 52 | 53 | tmp = TLV_utils.tlv_find_tag(self._tlvdata, 0xEA, num_results = 1) 54 | if len(tmp) == 0: 55 | raise ValueError, "Can't parse information file, tag 0xEA not found" 56 | tmp = TLV_utils.tlv_find_tag(tmp, 0x85, num_results = 1) 57 | if len(tmp) == 0: 58 | raise ValueError, "Can't parse information file, tag 0x85 not found" 59 | self._mainblob = tmp[0][2] 60 | 61 | tmp = self._mainblob 62 | some_id, tmp = tmp[:4], tmp[4:] 63 | 64 | ascii_field_len = ord(tmp[0]) 65 | tmp = tmp[1:] 66 | 67 | ascii_field, tmp = tmp[:ascii_field_len], tmp[ascii_field_len:] 68 | self._maindata = ascii_field.split(" ") 69 | 70 | if len(tmp) > 0: 71 | if tmp[0] == "\x01": 72 | tmp = tmp[1:] 73 | birthdate_bin, tmp = tmp[:4], tmp[4:] 74 | 75 | birthdate = binascii.b2a_hex(birthdate_bin) 76 | self._birthdate = datetime.date( int(birthdate[0:4]), int(birthdate[4:6]), int(birthdate[6:8]) ) 77 | 78 | if len(tmp) > 0: 79 | print "Warning: unparsed data trailing: %r" % tmp 80 | 81 | from_card = classmethod(from_card) 82 | 83 | def getter(index, encoding=None): 84 | def g(self): 85 | if self._maindata is None or len(self._maindata) <= index: 86 | return None 87 | if encoding is None: 88 | return unicode( self._maindata[index] ) 89 | else: 90 | return unicode( self._maindata[index], encoding = encoding ) 91 | 92 | return g 93 | 94 | def _get_alter(self): 95 | now = datetime.date.fromtimestamp( time.time() ) 96 | diff = now.year-self.geburtsdatum.year 97 | thisyearsbirthday = datetime.date( now.year, self.geburtsdatum.month, self.geburtsdatum.day ) 98 | if now < thisyearsbirthday: diff = diff - 1 99 | return diff 100 | 101 | def __str__(self): 102 | return "%s: %s %s" % (self.tickettyp, self.name_klar, self.abonr) 103 | 104 | 105 | tickettyp = property(getter(0)) 106 | rnummer = property(getter(1)) 107 | gueltigkeit = property(getter(2)) 108 | feld4 = property(getter(3)) 109 | name_raw = property(getter(4)) 110 | vorname = property(lambda self: self.name_raw and "".join(self.name_raw.split(",_")[1:]).replace("_", " ")) 111 | nachname = property(lambda self: self.name_raw and "".join(self.name_raw.split(",_")[:1]).replace("_", " ")) 112 | name_klar = property(lambda self: self.vorname + " " + self.nachname) 113 | schule = abonr = property(getter(5,'cp850')) 114 | geburtsdatum = property(lambda self: self._birthdate) 115 | alter = property(lambda self: self._birthdate and self._get_alter()) 116 | 117 | 118 | -------------------------------------------------------------------------------- /crypto_utils.py: -------------------------------------------------------------------------------- 1 | import sys, binascii, utils, random 2 | from Crypto.Cipher import DES3 3 | 4 | iv = '\x00' * 8 5 | PADDING = '\x80' + '\x00' * 7 6 | 7 | ## ******************************************************************* 8 | ## * Generic methods * 9 | ## ******************************************************************* 10 | def cipher(do_encrypt, cipherspec, key, data, iv = None): 11 | """Do a cryptographic operation. 12 | operation = do_encrypt ? encrypt : decrypt, 13 | cipherspec must be of the form "cipher-mode", or "cipher\"""" 14 | from Crypto.Cipher import DES3, DES, AES 15 | cipherparts = cipherspec.split("-") 16 | 17 | if len(cipherparts) > 2: 18 | raise ValueError, 'cipherspec must be of the form "cipher-mode" or "cipher"' 19 | elif len(cipherparts) == 1: 20 | cipherparts[1] = "ecb" 21 | 22 | c_class = locals().get(cipherparts[0].upper(), None) 23 | if c_class is None: 24 | raise ValueError, "Cipher '%s' not known, must be one of %s" % (cipherparts[0], ", ".join([e.lower() for e in dir() if e.isupper()])) 25 | 26 | mode = getattr(c_class, "MODE_" + cipherparts[1].upper(), None) 27 | if mode is None: 28 | raise ValueError, "Mode '%s' not known, must be one of %s" % (cipherparts[1], ", ".join([e.split("_")[1].lower() for e in dir(c_class) if e.startswith("MODE_")])) 29 | 30 | cipher = None 31 | if iv is None: 32 | cipher = c_class.new(key, mode) 33 | else: 34 | cipher = c_class.new(key, mode, iv) 35 | 36 | 37 | result = None 38 | if do_encrypt: 39 | result = cipher.encrypt(data) 40 | else: 41 | result = cipher.decrypt(data) 42 | 43 | del cipher 44 | return result 45 | 46 | def hash(hashspec, data): 47 | """Do a cryptographic hash operation. 48 | hashspec must be of the form "cipher\"""" 49 | from Crypto.Hash import SHA, RIPEMD, MD2, MD4, MD5 50 | 51 | if len(hashspec) != 3 and len(hashspec) != 6: 52 | raise ValueError, 'hashspec must be one of SHA, RIPEMD, MD2, MD4, MD5' 53 | 54 | h_class = locals().get(hashspec.upper(), None) 55 | if h_class is None: 56 | raise ValueError, "Hash '%s' not known, must be one of %s" % (hashspec, ", ".join([e.lower() for e in dir() if e.isupper()])) 57 | 58 | hash = h_class.new() 59 | hash.update(data) 60 | result = hash.digest() 61 | #m.hexdigest() 62 | 63 | del hash 64 | return result 65 | 66 | def operation_on_string(string1, string2, op): 67 | if len(string1) != len(string2): 68 | raise ValueError, "string1 and string2 must be of equal length" 69 | result = [] 70 | for i in range(len(string1)): 71 | result.append( chr(op(ord(string1[i]),ord(string2[i]))) ) 72 | return "".join(result) 73 | 74 | 75 | ## ******************************************************************* 76 | ## * Cyberflex specific methods * 77 | ## ******************************************************************* 78 | def verify_card_cryptogram(session_key, host_challenge, 79 | card_challenge, card_cryptogram): 80 | message = host_challenge + card_challenge 81 | expected = calculate_MAC(session_key, message, iv) 82 | 83 | print >>sys.stderr, "Original: %s" % binascii.b2a_hex(card_cryptogram) 84 | print >>sys.stderr, "Expected: %s" % binascii.b2a_hex(expected) 85 | 86 | return card_cryptogram == expected 87 | 88 | def calculate_host_cryptogram(session_key, card_challenge, 89 | host_challenge): 90 | message = card_challenge + host_challenge 91 | return calculate_MAC(session_key, message, iv) 92 | 93 | def calculate_MAC(session_key, message, iv): 94 | print >>sys.stderr, "Doing MAC for: %s" % utils.hexdump(message, indent = 17) 95 | 96 | cipher = DES3.new(session_key, DES3.MODE_CBC, iv) 97 | block_count = len(message) / cipher.block_size 98 | for i in range(block_count): 99 | cipher.encrypt(message[i*cipher.block_size:(i+1)*cipher.block_size]) 100 | 101 | last_block_length = len(message) % cipher.block_size 102 | last_block = (message[len(message)-last_block_length:]+PADDING)[:cipher.block_size] 103 | 104 | return cipher.encrypt( last_block ) 105 | 106 | def get_derivation_data(host_challenge, card_challenge): 107 | return card_challenge[4:8] + host_challenge[:4] + \ 108 | card_challenge[:4] + host_challenge[4:8] 109 | 110 | def get_session_key(auth_key, host_challenge, card_challenge): 111 | cipher = DES3.new(auth_key, DES3.MODE_ECB) 112 | return cipher.encrypt(get_derivation_data(host_challenge, card_challenge)) 113 | 114 | def generate_host_challenge(): 115 | random.seed() 116 | return "".join([chr(random.randint(0,255)) for e in range(8)]) 117 | 118 | def andstring(string1, string2): 119 | return operation_on_string(string1, string2, lambda a,b: a & b) 120 | 121 | if __name__ == "__main__": 122 | default_key = binascii.a2b_hex("404142434445464748494A4B4C4D4E4F") 123 | 124 | host_chal = binascii.a2b_hex("".join("89 45 19 BF BC 1A 5B D8".split())) 125 | card_chal = binascii.a2b_hex("".join("27 4D B7 EA CA 66 CE 44".split())) 126 | card_crypto = binascii.a2b_hex("".join("8A D4 A9 2D 9B 6B 24 E0".split())) 127 | 128 | session_key = get_session_key(default_key, host_chal, card_chal) 129 | print "Session-Key: ", utils.hexdump(session_key) 130 | 131 | print verify_card_cryptogram(session_key, host_chal, card_chal, card_crypto) 132 | 133 | host_crypto = calculate_host_cryptogram(session_key, card_chal, host_chal) 134 | print "Host-Crypto: ", utils.hexdump( host_crypto ) 135 | 136 | external_authenticate = binascii.a2b_hex("".join("84 82 01 00 10".split())) + host_crypto 137 | print utils.hexdump(calculate_MAC(session_key, external_authenticate, iv)) 138 | -------------------------------------------------------------------------------- /cyberflex-shell.e3p: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Python 8 | Qt 9 | 10 | 0.1 11 | Henryk Plötz 12 | henryk@ploetzli.ch 13 | 14 | 15 | utils.py 16 | 17 | 18 | crypto_utils.py 19 | 20 | 21 | cyberflex-shell.py 22 | 23 | 24 | cards 25 | cyberflex_card.py 26 | 27 | 28 | cards 29 | generic_card.py 30 | 31 | 32 | cards 33 | java_card.py 34 | 35 | 36 | cards 37 | __init__.py 38 | 39 | 40 | shell.py 41 | 42 | 43 | TLV_utils.py 44 | 45 | 46 | brutefid.py 47 | 48 | 49 | cards 50 | iso_7816_4_card.py 51 | 52 | 53 | cards 54 | tcos_card.py 55 | 56 | 57 | cards 58 | starcos_card.py 59 | 60 | 61 | tlvdecoder.py 62 | 63 | 64 | cards 65 | gsm_card.py 66 | 67 | 68 | cards 69 | building_blocks.py 70 | 71 | 72 | cards 73 | mtcos_card.py 74 | 75 | 76 | cards 77 | cardos_card.py 78 | 79 | 80 | cards 81 | postcard_card.py 82 | 83 | 84 | cards 85 | seccos_card.py 86 | 87 | 88 | parse-usbsnoop.py 89 | 90 | 91 | cards 92 | passport_application.py 93 | 94 | 95 | cards 96 | generic_application.py 97 | 98 | 99 | fingerpass.py 100 | 101 | 102 | cards 103 | rfid_card.py 104 | 105 | 106 | readpass.py 107 | 108 | 109 | gui 110 | __init__.py 111 | 112 | 113 | cards 114 | vrs_application.py 115 | 116 | 117 | cards 118 | nfc_application.py 119 | 120 | 121 | gui 122 | PassportGUI.py 123 | 124 | 125 | gui 126 | ireadyou.py 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | None 139 | {'status': [u'-v'], 'log': [], 'global': [u'-f'], 'update': [u'-dP'], 'remove': [u'-f'], 'add': [], 'tag': [u'-c'], 'export': [], 'diff': [u'-u3', u'-p'], 'commit': [], 'checkout': [], 'history': [u'-e', u'-a']} 140 | {} 141 | 142 | -------------------------------------------------------------------------------- /cyberflex-shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | import crypto_utils, utils, cards, readers, os, re, binascii, sys, exceptions, traceback, getopt, datetime 5 | from shell import Shell 6 | 7 | class Logger(object): 8 | def __init__(self, filename, stream, prefix = "# "): 9 | self.fp = file(filename, "w") 10 | self.stream = stream 11 | self.prefix = prefix 12 | self.need_prefix = True 13 | 14 | def println(self, string): 15 | if not self.need_prefix: 16 | self.fp.write("\n") 17 | self.need_prefix = True 18 | self.fp.write("\n".join(string.splitlines()) + "\n") 19 | 20 | def flush(self): 21 | return self.stream.flush() 22 | 23 | def close(self): 24 | self.fp.close() 25 | 26 | def writelines(self, lines): 27 | for line in lines: 28 | self.write(line) 29 | 30 | def write(self, line): 31 | if self.need_prefix: 32 | self.fp.write(self.prefix) 33 | self.need_prefix = False 34 | 35 | self.fp.write( ( ("\n"+self.prefix).join(line.splitlines()) ) ) 36 | if len(line) > 0 and line[-1] == "\n": 37 | self.fp.write("\n") 38 | self.need_prefix = True 39 | 40 | self.stream.write(line) 41 | 42 | class Cyberflex_Shell(Shell): 43 | def __init__(self, basename): 44 | self.print_backtrace = True 45 | self.reader = 0 46 | self.logger = None 47 | Shell.__init__(self, basename) 48 | self.register_commands(self, self.NOCARD_COMMANDS) 49 | self.set_prompt("(No card) ") 50 | 51 | def cmd_runscript(self, filename, ask = True): 52 | "Run an APDU script from a file" 53 | fh = file(filename) 54 | 55 | doit = not ask 56 | #ignored_SWs = ["\x62\x82"] 57 | ignored_SWs = [] 58 | 59 | for line in fh: 60 | if line[:2] == "//" or line[:1] == "#": 61 | continue 62 | 63 | if not doit: 64 | print "?? %s" % line.strip() 65 | print "Execute? (Yes/No/All/Exit) ", 66 | answer = sys.stdin.readline() 67 | if answer[0].lower() in ('y', "\n"): 68 | pass 69 | elif answer[0].lower() == 'n': 70 | continue 71 | elif answer[0].lower() == 'a': 72 | doit = True 73 | elif answer[0].lower() == 'e': 74 | return 75 | else: 76 | continue 77 | 78 | self.parse_and_execute(line) 79 | 80 | if self.card.sw_changed and not self.card.check_sw(self.card.last_sw) \ 81 | and self.card.last_sw not in ignored_SWs: 82 | 83 | print "SW(%s) was not OK. Ignore (i) or Abort (a)? " % binascii.hexlify(self.card.last_sw), 84 | answer = sys.stdin.readline() 85 | if answer[0].lower() in ('i', "\n"): 86 | pass 87 | elif answer[0].lower() == 'a': 88 | return 89 | elif answer[0] == 'S': 90 | ignored_SWs.append(self.card.last_sw) 91 | pass 92 | else: 93 | return 94 | 95 | def cmd_listreaders(self): 96 | "List the available readers" 97 | for i, (name, obj) in enumerate(readers.list_readers()): 98 | print "%i: %s" % (i,name) 99 | 100 | def cmd_enc(self, *args): 101 | "Encrypt or decrypt with openssl-like interface" 102 | 103 | args = list(args) 104 | 105 | MODE_DECRYPT = "-d" 106 | MODE_ENCRYPT = "-e" 107 | mode = MODE_ENCRYPT 108 | if "-d" in args: 109 | mode = MODE_DECRYPT 110 | 111 | input = None 112 | if "-in" in args: 113 | i = args.index("-in") 114 | input = args[i+1] 115 | 116 | if "-K" not in args: 117 | raise ValueError, "Must specify key with -K" 118 | i = args.index("-K") 119 | key = args[i+1] 120 | key = binascii.a2b_hex("".join(key.split())) 121 | 122 | iv = None 123 | if "-iv" in args: 124 | i = args.index("-iv") 125 | iv = args[i+1] 126 | iv = binascii.a2b_hex("".join(iv.split())) 127 | 128 | cipher = "des" 129 | if args[0][0] != "-": 130 | cipher = args[0] 131 | 132 | text = None 133 | if "-text" in args: 134 | if input is not None: 135 | raise ValueError, "Can't give -in and -text" 136 | i = args.index("-text") 137 | text = binascii.a2b_hex("".join(args[i+1].split())) 138 | 139 | if text is None: 140 | if input is None: 141 | text = self.card.last_result.data 142 | else: 143 | fp = file(input) 144 | text = fp.read() 145 | fp.close() 146 | 147 | result = crypto_utils.cipher(mode == MODE_ENCRYPT, cipher, key, text, iv) 148 | 149 | self.card.last_result = utils.R_APDU(result+"\x00\x00") 150 | print utils.hexdump(result) 151 | 152 | 153 | def cmd_eval(self, *args): 154 | "Execute raw python code" 155 | eval(" ".join(args)) 156 | print 157 | 158 | def cmd_atr(self, *args): 159 | """Print the ATR of the currently inserted card.""" 160 | print "ATR: %s" % utils.hexdump(self.card.reader.get_ATR(), short=True) 161 | 162 | def cmd_save_response(self, file_name, start = None, end = None): 163 | "Save the data in the last response to a file. start and end are optional" 164 | lastlen = len(self.card.last_result.data) 165 | if start is not None: 166 | start = (lastlen + (int(start,0) % lastlen) ) % lastlen 167 | else: 168 | start = 0 169 | if end is not None: 170 | end = (lastlen + (int(end,0) % lastlen) ) % lastlen 171 | else: 172 | end = lastlen 173 | 174 | fp = file(file_name, "w") 175 | try: 176 | fp.write(self.card.last_result.data[start:end]) 177 | finally: 178 | fp.close() 179 | 180 | def cmd_load_response(self, filename, start=None, end=None): 181 | "Load the data from a file and pretend it was the last response from the card. start and end are optional" 182 | fp = file(filename, "r") 183 | try: 184 | data = fp.read() 185 | finally: 186 | fp.close() 187 | datalen = len(data) 188 | 189 | if start is not None: 190 | start = (datalen + (int(start,0) % datalen) ) % datalen 191 | else: 192 | start = 0 193 | if end is not None: 194 | end = (datalen + (int(end,0) % datalen) ) % datalen 195 | else: 196 | end = datalen 197 | 198 | self.card.last_result = utils.R_APDU(data[start:end] + "\x00\x00") 199 | 200 | def cmd_disconnect(self, *args): 201 | "Close the connection to the currently inserted card" 202 | self.unregister_post_hook(self._print_sw) 203 | self.fallback = None 204 | self.unregister_pre_hook(self._clear_sw) 205 | self.unregister_pre_hook(self._update_prompt) 206 | self.unregister_commands(self.card) 207 | self.unregister_commands(self, self.CARD_COMMANDS) 208 | self.register_commands(self, self.NOCARD_COMMANDS) 209 | self.card.close_card() 210 | self.set_prompt("(No card) ") 211 | 212 | def cmd_reconnect(self, reader = None): 213 | "Re-open the connection to the card" 214 | self.cmd_disconnect() 215 | self.cmd_connect(reader) 216 | 217 | def cmd_fancy(self, *args): 218 | "Parse a fancy APDU and print the result" 219 | apdu = utils.C_APDU.parse_fancy(*args) 220 | data = apdu.render() 221 | if hasattr(self, "card"): 222 | self.card.last_result = utils.R_APDU(data+"\x00\x00") 223 | print utils.hexdump(data) 224 | 225 | def _update_prompt(self): 226 | self.set_prompt(self.card.get_prompt() + " ") 227 | 228 | def _clear_sw(self): 229 | self.card.sw_changed = False 230 | self.card.last_delta = None 231 | 232 | def do_fancy_apdu(self, *args): 233 | "Parse and transmit a fancy command" 234 | apdu = None 235 | try: 236 | if hasattr(self.card.COMMAND_CLASS, "parse_fancy"): 237 | apdu = self.card.COMMAND_CLASS.parse_fancy(*args) 238 | else: 239 | apdu = self.card.COMMAND_CLASS(*args) 240 | except ValueError: 241 | raise NotImplementedError 242 | 243 | if apdu is not None: 244 | return self.do_apdu(apdu) 245 | 246 | def do_normal_apdu(self, *args): 247 | "Transmit an APDU" 248 | apdu_string = "".join(args) 249 | if not utils.C_APDU._apduregex.match(apdu_string): 250 | raise NotImplementedError 251 | 252 | apdu_binary = binascii.a2b_hex("".join(apdu_string.split())) 253 | apdu = utils.C_APDU(apdu_binary) 254 | 255 | return self.do_apdu(apdu) 256 | 257 | def do_raw_apdu(self, *args): 258 | "Transmit a raw data string as an APDU" 259 | apdu_string = "".join(args) 260 | 261 | apdu_binary = binascii.a2b_hex("".join(apdu_string.split())) 262 | apdu = utils.Raw_APDU(apdu_binary) 263 | 264 | return self.do_apdu(apdu) 265 | 266 | def do_apdu(self, apdu): 267 | response = self.card.send_apdu(apdu) 268 | 269 | if len(response.data) > 0: ## The SW is already printed by _print_sw as a post_hook 270 | print utils.hexdump(response.data) 271 | 272 | def pause_log(self): 273 | if self.logger is not None: 274 | sys.stdout = self.logger.stream 275 | 276 | def unpause_log(self): 277 | if self.logger is not None: 278 | sys.stdout = self.logger 279 | 280 | def start_log(self, filename): 281 | if self.logger is not None: 282 | self.stop_log() 283 | self.logger = Logger(filename, sys.stdout) 284 | sys.stdout = self.logger 285 | print "Logging to %s" % filename 286 | try: 287 | self.logger.println( "# ATR of currently inserted card is: %s" % utils.hexdump(self.card.reader.get_ATR(), short=True) ) 288 | except (KeyboardInterrupt, SystemExit): 289 | raise 290 | except: 291 | pass 292 | self.register_pre_hook(self.pause_log) 293 | 294 | def stop_log(self): 295 | if self.logger is not None: 296 | print "Log stopped" 297 | sys.stdout = self.logger.stream 298 | self.logger.flush() 299 | self.logger = None 300 | self.unregister_pre_hook(self.pause_log) 301 | 302 | def parse_and_execute(self, line): 303 | if self.logger is not None: 304 | self.logger.println( self.logger.prefix + "\n" 305 | + self.logger.prefix + "=== " + datetime.datetime.now().isoformat(" ") + " " + ("="*49) ) 306 | self.logger.println(line) 307 | if self.logger is not None: 308 | self.unpause_log() 309 | result = Shell.parse_and_execute(self, line) 310 | return result 311 | 312 | def cmd_log(self, filename = None): 313 | "Start (when given a filename) or stop (otherwise) logging to a file" 314 | if filename is not None: 315 | date = datetime.datetime.now() 316 | vars = { 317 | "HOMEDIR": os.environ["HOME"], 318 | "ISOTIME": date.isoformat() 319 | } 320 | self.start_log(filename % vars) 321 | else: 322 | self.stop_log() 323 | 324 | def _print_sw(self): 325 | to_print = [] 326 | if self.card.sw_changed: 327 | to_print.append(self.card.decode_statusword()) 328 | 329 | if self.card.last_delta is not None: 330 | to_print.append("%0.03gs" % self.card.last_delta) 331 | 332 | if to_print: 333 | print ", ".join(to_print) 334 | 335 | def _find_driver_class(driver_name): 336 | for i in dir(cards): 337 | _obj = getattr(cards, i) 338 | if driver_name.lower() == i.lower(): 339 | return _obj 340 | if hasattr(_obj, "DRIVER_NAME") and driver_name.lower() in [e.lower() for e in getattr(_obj, "DRIVER_NAME")]: 341 | return _obj 342 | raise NameError, "Class not found" 343 | 344 | _find_driver_class = staticmethod(_find_driver_class) 345 | 346 | def cmd_unloaddriver(self, driver_name): 347 | "Remove a driver from the current connection" 348 | self.unregister_commands(self.card) 349 | try: 350 | self.card.remove_classes( [self._find_driver_class(driver_name)] ) 351 | finally: 352 | self.register_commands(self.card) 353 | 354 | def cmd_loaddriver(self, driver_name): 355 | "Add a driver to the current connection" 356 | self.unregister_commands(self.card) 357 | try: 358 | self.card.add_classes( [self._find_driver_class(driver_name)] ) 359 | finally: 360 | self.register_commands(self.card) 361 | 362 | def cmd_connect(self, reader = None): 363 | "Open the connection to a card" 364 | if reader is None: 365 | reader = self.reader 366 | 367 | reader_object = readers.connect_to(reader) 368 | self.card = cards.new_card_object(reader_object) 369 | 370 | self.unregister_commands(self, self.NOCARD_COMMANDS) 371 | self.register_commands(self, self.CARD_COMMANDS) 372 | self.register_commands(self.card) 373 | 374 | self.register_pre_hook(self._update_prompt) 375 | self.register_pre_hook(self._clear_sw) 376 | 377 | shell.fallback = self.do_fancy_apdu 378 | 379 | shell.register_post_hook(self._print_sw) 380 | 381 | COMMANDS = dict(Shell.COMMANDS) 382 | COMMANDS.update( { 383 | "list_readers": cmd_listreaders, 384 | "eval": cmd_eval, 385 | "save_response": cmd_save_response, 386 | "load_response": cmd_load_response, 387 | "fancy": cmd_fancy, 388 | "enc": cmd_enc, 389 | "log": cmd_log, 390 | } ) 391 | 392 | CARD_COMMANDS = { 393 | "atr": cmd_atr, 394 | "disconnect": cmd_disconnect, 395 | "reconnect": cmd_reconnect, 396 | "driver_load": cmd_loaddriver, 397 | "driver_unload": cmd_unloaddriver, 398 | "raw": do_raw_apdu, 399 | "run_script": cmd_runscript, 400 | } 401 | 402 | NOCARD_COMMANDS = { 403 | "connect": cmd_connect, 404 | } 405 | 406 | def usage(): 407 | print """Cyberflex shell 408 | Synopsis: cyberflex-shell.py [options] [scriptfiles] 409 | Options: 410 | -r, --reader Select the reader to use, either by 411 | index or by name 412 | -l, --list-readers List the available readers and their 413 | indices 414 | -n, --dont-connect Don't connect to the card on startup 415 | -y, --dont-ask Don't ask for confirmation for every 416 | command run from the scriptfiles 417 | -i, --force-interactive Force interactive mode after running 418 | scripts from the command line 419 | -h, --help This help 420 | """ 421 | 422 | OPTIONS = "nyih" 423 | LONG_OPTIONS = ["dont-connect","dont-ask","force-interactive","help"] 424 | exit_now = False 425 | dont_connect = False 426 | dont_ask = False 427 | force_interactive = False 428 | reader = None 429 | 430 | if __name__ == "__main__": 431 | 432 | helper = readers.CommandLineArgumentHelper() 433 | 434 | (options, arguments) = helper.getopt(sys.argv[1:], OPTIONS, LONG_OPTIONS) 435 | 436 | for (option, value) in options: 437 | if option in ("-h","--help"): 438 | usage() 439 | exit_now = True 440 | if option in ("-n","--dont-connect"): 441 | dont_connect = True 442 | if option in ("-y","--dont-ask"): 443 | dont_ask = True 444 | if option in ("-i","--force-interactive"): 445 | force_interactive = True 446 | 447 | if exit_now: 448 | sys.exit() 449 | del exit_now 450 | 451 | print "Cyberflex shell" 452 | shell = Cyberflex_Shell("cyberflex-shell") 453 | 454 | if not dont_connect: 455 | shell.cmd_connect(helper.reader) 456 | 457 | shell.run_startup() 458 | 459 | for filename in arguments: 460 | shell.cmd_runscript(filename, not dont_ask) 461 | 462 | if len(arguments) == 0 or force_interactive: 463 | shell.run() 464 | -------------------------------------------------------------------------------- /fingerpass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | import utils, cards, TLV_utils, sys, binascii, time, traceback, re, readers 5 | 6 | def fingerprint_rfid(card): 7 | # Need RFID 8 | if not isinstance(card, cards.rfid_card.RFID_Card): 9 | return [] 10 | 11 | uid = card.get_uid() 12 | 13 | return ["%02X" % ord(uid[0])] 14 | # FIXME: Determine ISO type and then return a value depending on A-fixed UID vs. A-random UID vs. B 15 | 16 | def fingerprint_7816(card): 17 | # Need ISO 7816-4 18 | if not isinstance(card, cards.iso_7816_4_card.ISO_7816_4_Card): 19 | return [] 20 | 21 | # Try a select MF, just in case ... 22 | try: 23 | card.change_dir() 24 | except (SystemExit, KeyboardInterrupt): 25 | raise 26 | except: 27 | traceback.print_exc() 28 | 29 | SHORT_SW_MAP = { 30 | "\x90\x00": 0, 31 | "\x69\x82": 1, # Security status not satisfied 32 | "\x6a\x82": 2, # File not found 33 | None: 3, 34 | } 35 | SHORT_SW_WIDTH = 2 36 | 37 | def detect_bac(card): 38 | "Check whether BAC is active and if yes what type of card-os (select not allowed, select allowed but read not allowed)" 39 | result = card.open_file("\x01\x01", 0x0c) # EF.DG1 40 | 41 | if result.sw == "\x90\x00": 42 | prefix = str(SHORT_SW_MAP[result.sw]) 43 | result = card.send_apdu(utils.C_APDU(card.APDU_READ_BINARY, p1=0, p2=0, le=1)) 44 | else: 45 | prefix = "" 46 | 47 | if SHORT_SW_MAP.has_key(result.sw): 48 | return prefix + str(SHORT_SW_MAP[result.sw]) 49 | else: 50 | return prefix + "%s:%s" % (SHORT_SW_MAP[None], binascii.b2a_hex(result.sw) ) 51 | 52 | def map_dg(card): 53 | "Get a map of which DGs exist and are readable/unreadable and with which SW they are unreadable" 54 | # Try to read 1 byte from each DG through READ BINARY with short file identifier 55 | responses = [card.send_apdu(utils.C_APDU(card.APDU_READ_BINARY, p1=i|0x80, p2=0, le=1)) for i in range(1,17)] 56 | 57 | result = [] 58 | exceptional = [] 59 | for response in responses: 60 | if SHORT_SW_MAP.has_key( response.sw ): 61 | result.append( SHORT_SW_MAP[response.sw] ) 62 | else: 63 | result.append( SHORT_SW_MAP[None] ) 64 | exceptional.append(response.sw) 65 | 66 | UNIT_FORMAT = "%X" 67 | UNIT_LEN = 4 # For hex in "%X" format. Would be 8 for hex in "%02X" format. 68 | compressed = [] 69 | current = 0 70 | count = 0 71 | for r in result: 72 | if count >= UNIT_LEN: 73 | compressed.append( current ) 74 | current = count = 0 75 | current = (current << SHORT_SW_WIDTH) | r 76 | count = count + SHORT_SW_WIDTH 77 | 78 | if count > 0: 79 | if not count >= UNIT_LEN: 80 | while count < UNIT_LEN: 81 | current = current << SHORT_SW_WIDTH 82 | count += SHORT_SW_WIDTH 83 | compressed.append( current ) 84 | current = count = 0 85 | 86 | 87 | return "".join( [UNIT_FORMAT % r for r in compressed] ) + ":".join( (len(exceptional) > 0 and [""] or []) + [binascii.b2a_hex(e) for e in exceptional] ) 88 | 89 | result = [] 90 | postfix = "" 91 | test_icao = card.select_application(card.resolve_symbolic_aid("mrtd"), le=None) 92 | if test_icao.sw == "\x67\x00": 93 | postfix = ":6700" # SELECT APPLICATION with P2=0 and without Le returns 6700 Wrong Length 94 | test_icao = card.select_application(card.resolve_symbolic_aid("mrtd"), le=None, P2=0x0c) 95 | 96 | if not card.check_sw(test_icao.sw, card.PURPOSE_SUCCESS): 97 | result.append("N"+postfix) # Not an ICAO MRTD 98 | else: 99 | result.append("P"+postfix) # An ICAO MRTD 100 | 101 | bac = detect_bac(card) 102 | result.append(bac) # BAC status 103 | 104 | dgmap = map_dg(card) 105 | result.append(dgmap) # Data Group map 106 | 107 | return result 108 | 109 | def fingerprint(card): 110 | def compress_atr(atr): 111 | numhist = ord(atr[1]) & 0x0f 112 | if binascii.a2b_hex( "3B8%X8001" % numhist ) == atr[:4]: 113 | # Contactless, conforming to PC/SC part 3 section 3.1.3.2.3 114 | 115 | if atr[4:6] == "\x80\x4f": # Status indicator in compact-tlv object 116 | si_len = ord(atr[6]) 117 | aid = atr[7:7+si_len] 118 | 119 | if aid[:5] == "\xa0\x00\x00\x03\x06": # RID of PC/SC Workgroup 120 | standard_and_name = aid[5:] 121 | if standard_and_name[3:] == "\x00" * (len(standard_and_name)-3): 122 | return "1:%s" % binascii.b2a_hex(standard_and_name[:3]) # RFU bytes unset 123 | else: 124 | return "2:%s" % binascii.b2a_hex(standard_and_name) # RFU bytes set 125 | 126 | return "0:%s" % binascii.b2a_hex(atr[4:]) 127 | else: 128 | # Not contactless (or not conforming) 129 | return "3:%s" % binascii.b2a_hex(atr) 130 | return "" 131 | 132 | result = [] 133 | 134 | atr = card.get_atr() 135 | try: 136 | catr = compress_atr(atr) 137 | except (KeyboardInterrupt, SystemExit): 138 | raise 139 | except: # Any error in the ATR processing 140 | catr = "F:%s" % binascii.b2a_hex(atr) 141 | result.append( catr ) 142 | result.extend( fingerprint_7816(card) ) 143 | result.extend( fingerprint_rfid(card) ) 144 | 145 | return ",".join(result) 146 | 147 | def match_fingerprint(fingerprint, database="fingerprints.txt"): 148 | fp = file(database, "r") 149 | 150 | results = [] 151 | current_result = [] 152 | first_line = True 153 | matched = False 154 | 155 | def do_match(line, fingerprint): 156 | return re.match(line.strip(), fingerprint.strip()) is not None 157 | 158 | for line in fp.readlines(): 159 | if line[0] == "#": 160 | continue 161 | 162 | if line.strip() == "": 163 | matched = False 164 | if len(current_result) > 0: 165 | results.append(current_result) 166 | current_result = [] 167 | elif not line[0].isspace(): 168 | if do_match(line, fingerprint): 169 | matched = True 170 | else: 171 | matched = False 172 | elif matched: 173 | current_result.append(line.strip()) 174 | 175 | if len(current_result) > 0: 176 | results.append(current_result) 177 | current_result = [] 178 | 179 | fp.close() 180 | return ["\n".join(e) for e in results] 181 | 182 | if __name__ == "__main__": 183 | c = readers.CommandLineArgumentHelper() 184 | (options, arguments) = c.getopt(sys.argv[1:]) 185 | 186 | card_object = c.connect() 187 | card = cards.new_card_object(card_object) 188 | cards.generic_card.DEBUG = False 189 | 190 | print >>sys.stderr, "Using %s" % card.DRIVER_NAME 191 | 192 | if isinstance(card, cards.rfid_card.RFID_Card): 193 | print "UID: %s" % utils.hexdump(card.get_uid(), short=True) 194 | fp = fingerprint(card) 195 | print "Fingerprint: %s" % fp 196 | matches = match_fingerprint(fp) 197 | if len(matches) > 1: 198 | print "Matched as: \n\t+ %s" % "\nor\t+ ".join( ["\n\t ".join(e.split("\n")) for e in matches] ) 199 | elif len(matches) == 1: 200 | if len(matches[0].split("\n")) == 1: 201 | print "Matched as: %s" % matches[0] 202 | else: 203 | print "Matched as: \n\t%s" % "\n\t".join( matches[0].split("\n") ) 204 | 205 | -------------------------------------------------------------------------------- /fingerprints.txt: -------------------------------------------------------------------------------- 1 | 1:0b0014 2 | ICode 3 | 4 | 0:8080,N 5 | DESfire 6 | 7 | 1:030003 8 | Mifare Ultralight 9 | 10 | 1:030001 11 | Mifare Classic 1k 12 | 13 | 0:80919131f06477e30300838290001c,P[^,]*, 14 | Belgian Passport 15 | (imported from Ludovic Rousseau) 16 | 17 | 0:006404150102009000ee,P:6700,1,569AAA9A 18 | Electronic Passport Specimen, issued by Bundesdruckerei, Germany 19 | # ATS: 0e 78 33 c4 02 00 64 04 15 01 02 00 90 00 20 | 21 | 0:[^,]*,P:6700,1,5AAAAAAA,08 22 | German Passport 23 | # Observed ATRS: 24 | # Passport issued Nov 06: 3b 8b 80 01 00 64 04 11 01 01 31 80 00 90 00 5a 25 | # corresponding fingerprint: 0:00640411010131800090005a,P:6700,1,5AAAAAAA,08 26 | # Passport issued Apr 07: 3b 89 80 01 00 64 04 15 01 02 00 90 00 ee 27 | # corresponding fingerprint: 0:006404150102009000ee,P:6700,1,5AAAAAAA,08 28 | 29 | 30 | 0:4a434f5034315632324d,P,1,55555555 31 | New Zealandic Passport 32 | # These have a fixed 4-byte UID 33 | 34 | 0:01,P,1,55555555,08 35 | Dutch passport -------------------------------------------------------------------------------- /gui/PassportGUI.py: -------------------------------------------------------------------------------- 1 | import gtk,gtk.glade,gobject 2 | import sys, os, time, TLV_utils, cards 3 | 4 | class Converter: 5 | SUPPORTS = ["jp2"] 6 | MAXSIZE = 400 7 | 8 | def convert(type, image_data): 9 | stdin, stdout = os.popen2("convert %s:- -resize %sx%s bmp:-" % (type, Converter.MAXSIZE, Converter.MAXSIZE)) 10 | n = time.time() 11 | stdin.write(image_data) 12 | stdin.close() 13 | return_data = stdout.read() 14 | stdout.close() 15 | #print "Took", time.time()-n, "seconds for conversion" 16 | return return_data 17 | convert = staticmethod(convert) 18 | 19 | class PassportGUI: 20 | GLADE_FILE = "gui/passport/passport.glade" 21 | 22 | def __init__(self): 23 | "Create and show main window." 24 | self.passport = None 25 | self.format_strings = {} 26 | self.images = [] 27 | self.now_showing = 0 28 | self.main_window_xml = gtk.glade.XML(self.GLADE_FILE, "main") 29 | self.main_window = self.main_window_xml.get_widget("main") 30 | self.card_factory = None 31 | 32 | signals = { 33 | "on_exit_clicked": self.exit_clicked, 34 | "on_clear_clicked": self.clear_clicked, 35 | "on_open_clicked": self.open_clicked, 36 | "on_main_delete_event": self.exit_clicked, 37 | "on_main_destroy": gtk.main_quit, 38 | "on_next_image_clicked": self.next_image, 39 | "on_prev_image_clicked": self.prev_image, 40 | "on_mrz_entry1_activate": self.mrz1_activate, 41 | "on_mrz_entry2_activate": self.mrz2_activate, 42 | } 43 | self.main_window_xml.signal_autoconnect(signals) 44 | 45 | def mrz1_activate(self, widget, event=None, data=None): 46 | self.main_window_xml.get_widget("mrz_entry2").grab_focus() 47 | 48 | def mrz2_activate(self, widget, event=None, data=None): 49 | self.main_window_xml.get_widget("open").clicked() 50 | self.main_window_xml.get_widget("mrz_entry1").grab_focus() 51 | 52 | def exit_clicked(self, widget, event=None, data=None): 53 | gtk.main_quit() 54 | return True 55 | 56 | def run(self): 57 | gtk.gdk.threads_init() 58 | gtk.main() 59 | 60 | def lookup_country(passport, contents): 61 | return passport.COUNTRY_CODES.get( contents[0], ("Unknown Code", "") ) 62 | 63 | def split_name(passport, contents): 64 | return (contents[0][0], " ".join(contents[0][1:])) 65 | 66 | def parse_date(passport, contents): 67 | year, month, day = int(contents[0][0:2]), int(contents[0][2:4]), int(contents[0][4:6]) 68 | if year < 30: # Yeah, two-digit years for the win! 69 | year = 2000 + year 70 | else: 71 | year = 1900 + year 72 | 73 | return ("%04i-%02i-%02i" % (year, month, day), ) 74 | 75 | def format_mrz(passport, contents): 76 | mrz = contents[0] 77 | if contents[1] is not None: 78 | mrz = contents[1] 79 | 80 | return [e.replace("<","<") for e in mrz] 81 | 82 | s = lambda a,b: (str(b[0]),) 83 | PROPERTY_TRANSFORMATIONS = [ 84 | # This code implies an m:n relation from passport object properties to 85 | # displayed fields. This is a sequence of ( (passport_field, ...) transform_callable, (destination_field, ...)) 86 | # transform_callable will be called with a reference to the passport and a list of the values of (passport_field, ...) 87 | # and must return len( (destination_field, ...) ) values wich will then be displayed in the corresponding 88 | # destination fields. 89 | 90 | ( ("type",), s, ("type",)), 91 | ( ("issuer",), s, ("issuer",)), 92 | ( ("issuer",), lookup_country, ("issuer_clear1", "issuer_clear2")), 93 | ( ("name",), split_name, ("surname", "firstname")), 94 | ( ("document_no",), s, ("document_no",)), 95 | ( ("nationality",), s, ("nationality",)), 96 | ( ("nationality",), lookup_country, ("nationality_clear1", "nationality_clear2")), 97 | ( ("date_of_birth",), parse_date, ("dob",)), 98 | ( ("sex",), s, ("sex",)), 99 | ( ("expiration_date",), parse_date, ("doe",)), 100 | ( ("optional",), s, ("optional",)), 101 | ( ("given_mrz", "dg1_mrz"), format_mrz, ("mrz1", "mrz2") ), 102 | ] 103 | del s 104 | 105 | def set_passport(self, passport): 106 | self.passport = passport 107 | 108 | for sources, transform, destinations in self.PROPERTY_TRANSFORMATIONS: 109 | values = [getattr(passport, src) for src in sources] 110 | transformed = transform(passport, values) 111 | for index, dst in enumerate(destinations): 112 | widget = self.main_window_xml.get_widget(dst) 113 | if not self.format_strings.has_key(dst): 114 | self.format_strings[dst] = widget.get_label() 115 | widget.set_label( self.format_strings[dst] % transformed[index] ) 116 | 117 | data = [] 118 | if hasattr(passport, "dg2_cbeff") and passport.dg2_cbeff is not None: 119 | for biometric in passport.dg2_cbeff.biometrics: 120 | data = data + [(a,b,"Encoded Face") for (a,b) in biometric.get_images()] 121 | 122 | for dg, tag, type in ( ("dg5", 0x5F40, "Displayed Portrait"), ("dg7", 0x5F43, "Displayed Signature or Usual Mark") ): 123 | if hasattr(passport, "%s_tlv" % dg): 124 | structure = getattr(passport, "%s_tlv" % dg) 125 | if structure is not None: 126 | hits = TLV_utils.tlv_find_tag(structure, tag) 127 | for t,l,v in hits: 128 | data.append( ("jpg",v,type) ) 129 | 130 | self._set_images(data) 131 | 132 | def clear_display(self): 133 | for sources, transform, destinations in self.PROPERTY_TRANSFORMATIONS: 134 | for index, dst in enumerate(destinations): 135 | widget = self.main_window_xml.get_widget(dst) 136 | if not self.format_strings.has_key(dst): 137 | self.format_strings[dst] = widget.get_label() 138 | widget.set_label( "" ) 139 | self._set_images([]) 140 | self.main_window_xml.get_widget("mrz_entry1").set_text("") 141 | self.main_window_xml.get_widget("mrz_entry2").set_text("") 142 | self.update_image_shown() 143 | 144 | def clear_clicked(self, widget, event=None, data=None): 145 | self.clear_display() 146 | 147 | def open_clicked(self, widget, event=None, data=None): 148 | mrz1 = self.main_window_xml.get_widget("mrz_entry1").get_text() 149 | mrz2 = self.main_window_xml.get_widget("mrz_entry2").get_text() 150 | mrz = [e.strip().upper().replace(";","<") for e in mrz1, mrz2] 151 | 152 | self.clear_display() 153 | 154 | self.main_window_xml.get_widget("mrz_entry1").set_text(mrz[0]) 155 | self.main_window_xml.get_widget("mrz_entry2").set_text(mrz[1]) 156 | 157 | while gtk.events_pending(): 158 | gtk.main_iteration_do(block=False) 159 | 160 | if self.card_factory: 161 | try: 162 | card_object = self.card_factory.connect() 163 | card = cards.new_card_object(card_object) 164 | cards.generic_card.DEBUG = False 165 | 166 | print >>sys.stderr, "Using %s" % card.DRIVER_NAME 167 | 168 | p = cards.passport_application.Passport.from_card(card, mrz) 169 | 170 | self.set_passport(p) 171 | except KeyboardInterrupt,SystemExit: raise 172 | except: 173 | import traceback 174 | traceback.print_exc() 175 | 176 | def set_card_factory(self, c): 177 | self.card_factory = c 178 | 179 | def _set_images(self, data): 180 | self.images = [] 181 | for type, image_data, description in data: 182 | if type in Converter.SUPPORTS: 183 | image_data = Converter.convert(type, image_data) 184 | 185 | loader = gtk.gdk.PixbufLoader() 186 | loader.write(image_data) 187 | loader.close() 188 | pixbuf = loader.get_pixbuf() 189 | 190 | self.images.append( (pixbuf, description) ) 191 | 192 | self.update_image_shown() 193 | 194 | def update_image_shown(self, add=0): 195 | self.now_showing = self.now_showing + add 196 | 197 | if self.now_showing >= len(self.images): 198 | self.now_showing = len(self.images)-1 199 | if self.now_showing < 0: 200 | self.now_showing = 0 201 | 202 | if len(self.images) > 0: 203 | pixbuf, description = self.images[self.now_showing] 204 | else: 205 | pixbuf, description = None, "No image loaded" 206 | 207 | label = self.main_window_xml.get_widget("image_label") 208 | if not self.format_strings.has_key("image_label"): 209 | self.format_strings["image_label"] = label.get_label() 210 | label.set_label( self.format_strings["image_label"] % { 211 | "num": len(self.images) > 0 and (self.now_showing+1) or 0, 212 | "count": len(self.images), 213 | "description": description, 214 | } ) 215 | 216 | if not self.format_strings.has_key("image"): 217 | self.format_strings["image"] = self.main_window_xml.get_widget("image").get_stock() 218 | 219 | if pixbuf is not None: 220 | self.main_window_xml.get_widget("image").set_from_pixbuf(pixbuf) 221 | else: 222 | self.main_window_xml.get_widget("image").set_from_stock(*self.format_strings["image"]) 223 | 224 | self.main_window_xml.get_widget("prev_image").set_property("sensitive", self.now_showing > 0) 225 | self.main_window_xml.get_widget("next_image").set_property("sensitive", self.now_showing < len(self.images)-1) 226 | 227 | def next_image(self, widget): 228 | self.update_image_shown(+1) 229 | 230 | def prev_image(self, widget): 231 | self.update_image_shown(-1) 232 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | from PassportGUI import * 2 | from ireadyou import * 3 | -------------------------------------------------------------------------------- /gui/ireadyou.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henryk/cyberflex-shell/0f7584aab47400ea346c938a3f13b7a099c22f2c/gui/ireadyou.py -------------------------------------------------------------------------------- /gui/ireadyou/ireadyou.glade: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | True 8 | iReadYou 9 | GTK_WINDOW_TOPLEVEL 10 | GTK_WIN_POS_NONE 11 | False 12 | True 13 | False 14 | True 15 | False 16 | False 17 | GDK_WINDOW_TYPE_HINT_NORMAL 18 | GDK_GRAVITY_NORTH_WEST 19 | True 20 | False 21 | 22 | 23 | 24 | 25 | 26 | True 27 | 0 28 | 0 29 | 1 30 | 1 31 | 0 32 | 0 33 | 0 34 | 0 35 | 36 | 37 | 38 | True 39 | False 40 | 0 41 | 42 | 43 | 44 | True 45 | 0.5 46 | 0.5 47 | 1 48 | 1 49 | 0 50 | 0 51 | 0 52 | 0 53 | 54 | 55 | 56 | True 57 | 0 58 | 0.5 59 | GTK_SHADOW_NONE 60 | 61 | 62 | 63 | True 64 | 0 65 | 0 66 | 0 67 | 0 68 | 0 69 | 0 70 | 12 71 | 0 72 | 73 | 74 | 75 | True 76 | True 77 | True 78 | True 79 | GTK_POS_TOP 80 | False 81 | False 82 | 83 | 84 | 85 | True 86 | False 87 | 0 88 | 89 | 90 | 91 | True 92 | label11 93 | False 94 | False 95 | GTK_JUSTIFY_LEFT 96 | False 97 | False 98 | 0.5 99 | 0.5 100 | 0 101 | 0 102 | PANGO_ELLIPSIZE_NONE 103 | -1 104 | False 105 | 0 106 | 107 | 108 | 0 109 | False 110 | False 111 | 112 | 113 | 114 | 115 | False 116 | True 117 | 118 | 119 | 120 | 121 | 122 | True 123 | label10 124 | False 125 | False 126 | GTK_JUSTIFY_LEFT 127 | False 128 | False 129 | 0.5 130 | 0.5 131 | 0 132 | 0 133 | PANGO_ELLIPSIZE_NONE 134 | -1 135 | False 136 | 0 137 | 138 | 139 | tab 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | True 150 | <b>Kartendaten</b> 151 | False 152 | True 153 | GTK_JUSTIFY_LEFT 154 | False 155 | False 156 | 0.5 157 | 0.5 158 | 0 159 | 0 160 | PANGO_ELLIPSIZE_NONE 161 | -1 162 | False 163 | 0 164 | 165 | 166 | label_item 167 | 168 | 169 | 170 | 171 | 172 | 173 | 0 174 | True 175 | True 176 | 177 | 178 | 179 | 180 | 181 | True 182 | 0 183 | 0.5 184 | GTK_SHADOW_NONE 185 | 186 | 187 | 188 | True 189 | 0 190 | 0 191 | 0 192 | 1 193 | 0 194 | 0 195 | 12 196 | 0 197 | 198 | 199 | 200 | True 201 | False 202 | 0 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | True 223 | <b>Status</b> 224 | False 225 | True 226 | GTK_JUSTIFY_LEFT 227 | False 228 | False 229 | 0.5 230 | 0.5 231 | 0 232 | 0 233 | PANGO_ELLIPSIZE_NONE 234 | -1 235 | False 236 | 0 237 | 238 | 239 | label_item 240 | 241 | 242 | 243 | 244 | 0 245 | False 246 | True 247 | 248 | 249 | 250 | 251 | 252 | True 253 | True 254 | False 255 | 0 256 | 257 | 258 | 259 | True 260 | 0.5 261 | 0.5 262 | 1 263 | 1 264 | 0 265 | 0 266 | 12 267 | 0 268 | 269 | 270 | 271 | True 272 | True 273 | GTK_POLICY_AUTOMATIC 274 | GTK_POLICY_AUTOMATIC 275 | GTK_SHADOW_IN 276 | GTK_CORNER_TOP_LEFT 277 | 278 | 279 | 280 | True 281 | True 282 | False 283 | False 284 | True 285 | GTK_JUSTIFY_LEFT 286 | GTK_WRAP_NONE 287 | True 288 | 0 289 | 0 290 | 0 291 | 0 292 | 0 293 | 0 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | True 305 | <b>Log</b> 306 | False 307 | True 308 | GTK_JUSTIFY_LEFT 309 | False 310 | False 311 | 0.5 312 | 0.5 313 | 0 314 | 0 315 | PANGO_ELLIPSIZE_NONE 316 | -1 317 | False 318 | 0 319 | 320 | 321 | label_item 322 | 323 | 324 | 325 | 326 | 0 327 | True 328 | True 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | -------------------------------------------------------------------------------- /gui/ireadyou/ireadyou.gladep: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iReadYou 6 | ireadyou 7 | FALSE 8 | 9 | -------------------------------------------------------------------------------- /gui/passport/passport.gladep: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Passport 6 | passport 7 | FALSE 8 | 9 | -------------------------------------------------------------------------------- /oids.txt: -------------------------------------------------------------------------------- 1 | # From openssl 2 | 1.2.840.10045.1.1 prime-field 3 | 1.2.840.10045.2.1 id-ecPublicKey 4 | 1.2.840.10045.4.1 ecdsa-with-SHA1 5 | 1.2.840.113549 rsadsi RSA Security Data Inc. 6 | 1.2.840.113549.1 pkcs PKCS (Public Key Cryptography Standards). 7 | 1.2.840.113549.1.1 pkcs-1 PKCS#1 (Public Key Cryptography Standards - 1) 8 | 1.2.840.113549.1.1.1 rsaEncryption 9 | 1.2.840.113549.1.1.2 md2WithRSAEncryption 10 | 1.2.840.113549.1.1.3 md4withRSAEncryption 11 | 1.2.840.113549.1.1.4 md5WithRSAEncryption 12 | 1.2.840.113549.1.1.5 sha1WithRSAEncryption 13 | 1.2.840.113549.1.7.2 pkcs7-signedData 14 | 1.2.840.113549.1.9.21 localKeyID 15 | 1.2.840.113549.1.9.3 contentType 16 | 1.2.840.113549.1.9.4 messageDigest 17 | 1.3.14.3.2.26 sha1 18 | 1.3.36.3.3.1.2 ripemd160WithRSA 19 | 2.5 X500 20 | 2.5.4 X509 21 | 2.5.4.10 organizationName 22 | 2.5.4.11 organizationalUnitName 23 | 2.5.4.3 commonName 24 | 2.5.4.5 serialNumber 25 | 2.5.4.6 countryName 26 | 2.5.29.15 keyUsage X509v3 Key Usage 27 | 2.5.29.16 privateKeyUsagePeriod X509v3 Private Key Usage Period 28 | 2.5.29.32 certificatePolicies X509v3 Certificate Policies 29 | 2.5.29.35 authorityKeyIdentifier X509v3 Authority Key Identifier 30 | 31 | # I made this one up --Henryk 32 | 2.23.136.1.1.1 icao-sodData The data contained in the EF.SOD 33 | 34 | # From tr-03110-eac-1.0.pdf 35 | 0.4.0.127.0.7 bsi-de Base OID for BSI, Germany 36 | 0.4.0.127.0.7.2.2.1 id-CA Chip Authentication object identifiers 37 | 0.4.0.127.0.7.2.2.1.1 id-CA-DH DH Algorithm suite for Chip Authentication 38 | 0.4.0.127.0.7.2.2.1.2 id-CA-ECDH ECDH Algorithm suite for Chip Authentication 39 | 40 | # From TR-03110_v201_pdf.pdf 41 | 0.4.0.127.0.7.3.2 id-eID eID application 42 | 0.4.0.127.0.7.3.2.1 id-SecurityObject 43 | 44 | 0.4.0.127.0.7.2.2.1 id-PK Chip Authentication Public Key 45 | 46 | 0.4.0.127.0.7.2.2.1.1 id-PK-DH 47 | 0.4.0.127.0.7.2.2.1.2 id-PK-ECDH 48 | 49 | 0.4.0.127.0.7.2.2.2 id-TA Terminal Authentication 50 | 51 | 0.4.0.127.0.7.2.2.2.1 id-TA-RSA 52 | 0.4.0.127.0.7.2.2.2.1.1 id-TA-RSA-v1-5-SHA-1 53 | 0.4.0.127.0.7.2.2.2.1.2 id-TA-RSA-v1-5-SHA-256 54 | 0.4.0.127.0.7.2.2.2.1.3 id-TA-RSA-PSS-SHA-1 55 | 0.4.0.127.0.7.2.2.2.1.4 id-TA-RSA-PSS-SHA-256 56 | 57 | 0.4.0.127.0.7.2.2.2.2 id-TA-ECDSA 58 | 0.4.0.127.0.7.2.2.2.2.1 id-TA-ECDSA-SHA-1 59 | 0.4.0.127.0.7.2.2.2.2.2 id-TA-ECDSA-SHA-224 60 | 0.4.0.127.0.7.2.2.2.2.3 id-TA-ECDSA-SHA-256 61 | 62 | 0.4.0.127.0.7.2.2.3 id-CA Chip Authentication protocol 63 | 64 | 0.4.0.127.0.7.2.2.3.1 id-CA-DH 65 | 0.4.0.127.0.7.2.2.3.1.1 id-CA-DH-3DES-CBC-CBC 66 | 0.4.0.127.0.7.2.2.3.1.2 id-CA-DH-AES-CBC-CMAC-128 67 | 0.4.0.127.0.7.2.2.3.1.3 id-CA-DH-AES-CBC-CMAC-192 68 | 0.4.0.127.0.7.2.2.3.1.4 id-CA-DH-AES-CBC-CMAC-256 69 | 70 | 0.4.0.127.0.7.2.2.3.2 id-CA-ECDH 71 | 0.4.0.127.0.7.2.2.3.2.1 id-CA-ECDH-3DES-CBC-CBC 72 | 0.4.0.127.0.7.2.2.3.2.2 id-CA-ECDH-AES-CBC-CMAC-128 73 | 0.4.0.127.0.7.2.2.3.2.3 id-CA-ECDH-AES-CBC-CMAC-192 74 | 0.4.0.127.0.7.2.2.3.2.4 id-CA-ECDH-AES-CBC-CMAC-256 75 | 76 | 0.4.0.127.0.7.2.2.4 id-PACE PACE protocol 77 | 78 | 0.4.0.127.0.7.2.2.4.1 id-PACE-DH-GM 79 | 0.4.0.127.0.7.2.2.4.1.1 id-PACE-DH-GM-3DES-CBC-CBC 80 | 0.4.0.127.0.7.2.2.4.1.2 id-PACE-DH-GM-AES-CBC-CMAC-128 81 | 0.4.0.127.0.7.2.2.4.1.3 id-PACE-DH-GM-AES-CBC-CMAC-192 82 | 0.4.0.127.0.7.2.2.4.1.4 id-PACE-DH-GM-AES-CBC-CMAC-256 83 | 84 | 0.4.0.127.0.7.2.2.4.2 id-PACE-ECDH-GM 85 | 0.4.0.127.0.7.2.2.4.2.1 id-PACE-ECDH-GM-3DES-CBC-CBC 86 | 0.4.0.127.0.7.2.2.4.2.2 id-PACE-ECDH-GM-AES-CBC-CMAC-128 87 | 0.4.0.127.0.7.2.2.4.2.3 id-PACE-ECDH-GM-AES-CBC-CMAC-192 88 | 0.4.0.127.0.7.2.2.4.2.4 id-PACE-ECDH-GM-AES-CBC-CMAC-256 89 | 90 | 0.4.0.127.0.7.2.2.4.3 id-PACE-DH-IM 91 | 0.4.0.127.0.7.2.2.4.3.1 id-PACE-DH-IM-3DES-CBC-CBC 92 | 0.4.0.127.0.7.2.2.4.3.2 id-PACE-DH-IM-AES-CBC-CMAC-128 93 | 0.4.0.127.0.7.2.2.4.3.3 id-PACE-DH-IM-AES-CBC-CMAC-192 94 | 0.4.0.127.0.7.2.2.4.3.4 id-PACE-DH-IM-AES-CBC-CMAC-256 95 | 96 | 0.4.0.127.0.7.2.2.4.4 id-PACE-ECDH-IM 97 | 0.4.0.127.0.7.2.2.4.4.1 id-PACE-ECDH-IM-3DES-CBC-CBC 98 | 0.4.0.127.0.7.2.2.4.4.2 id-PACE-ECDH-IM-AES-CBC-CMAC-128 99 | 0.4.0.127.0.7.2.2.4.4.3 id-PACE-ECDH-IM-AES-CBC-CMAC-192 100 | 0.4.0.127.0.7.2.2.4.4.4 id-PACE-ECDH-IM-AES-CBC-CMAC-256 101 | 102 | 0.4.0.127.0.7.2.2.5 id-RI Restricted Identification protocol 103 | 104 | 0.4.0.127.0.7.2.2.5.1 id-RI-DH 105 | 0.4.0.127.0.7.2.2.5.1.1 id-RI-DH-SHA-1 106 | 0.4.0.127.0.7.2.2.5.1.2 id-RI-DH-SHA-224 107 | 0.4.0.127.0.7.2.2.5.1.3 id-RI-DH-SHA-256 108 | 109 | 0.4.0.127.0.7.2.2.5.2 id-RI-ECDH 110 | 0.4.0.127.0.7.2.2.5.2.1 id-RI-ECDH-SHA-1 111 | 0.4.0.127.0.7.2.2.5.2.2 id-RI-ECDH-SHA-224 112 | 0.4.0.127.0.7.2.2.5.2.3 id-RI-ECDH-SHA-256 113 | 114 | 0.4.0.127.0.7.2.2.6 id-CI Card Info locator 115 | 116 | # From BSI-TR-03111_pdf.pdf 117 | 0.4.0.127.0.7.1.1 id-ecc Root identifier for elliptic curve cryptography 118 | 119 | 0.4.0.127.0.7.1.1.2.2 id-ecTLVKeyFormat 120 | 0.4.0.127.0.7.1.1.2.2.1 id-ecTLVPublicKey 121 | 122 | 0.4.0.127.0.7.1.1.4.1 ecdsa-plain-signatures 123 | 0.4.0.127.0.7.1.1.4.1.1 ecdsa-plain-SHA1 124 | 0.4.0.127.0.7.1.1.4.1.2 ecdsa-plain-SHA224 125 | 0.4.0.127.0.7.1.1.4.1.3 ecdsa-plain-SHA256 126 | 0.4.0.127.0.7.1.1.4.1.4 ecdsa-plain-SHA384 127 | 0.4.0.127.0.7.1.1.4.1.5 ecdsa-plain-SHA512 128 | 0.4.0.127.0.7.1.1.4.1.6 ecdsa-plain-RIPEMD160 129 | 130 | 0.4.0.127.0.7.1.1.5.1 ecka-eg ElGamal Key Agreement 131 | 132 | 0.4.0.127.0.7.1.1.5.1.1 ecka-eg-X963KDF 133 | 0.4.0.127.0.7.1.1.5.1.1.1 ecka-eg-X963KDF-SHA1 134 | 0.4.0.127.0.7.1.1.5.1.1.2 ecka-eg-X963KDF-SHA224 135 | 0.4.0.127.0.7.1.1.5.1.1.3 ecka-eg-X963KDF-SHA256 136 | 0.4.0.127.0.7.1.1.5.1.1.4 ecka-eg-X963KDF-SHA385 137 | 0.4.0.127.0.7.1.1.5.1.1.5 ecka-eg-X963KDF-SHA512 138 | 0.4.0.127.0.7.1.1.5.1.1.6 ecka-eg-X963KDF-RIPEMD160 139 | 140 | 0.4.0.127.0.7.1.1.5.1.2 ecka-eg-SessionKDF 141 | 0.4.0.127.0.7.1.1.5.1.2.1 ecka-eg-SessionKDF-3DES 142 | 0.4.0.127.0.7.1.1.5.1.2.2 ecka-eg-SessionKDF-AES128 143 | 0.4.0.127.0.7.1.1.5.1.2.3 ecka-eg-SessionKDF-AES192 144 | 0.4.0.127.0.7.1.1.5.1.2.4 ecka-eg-SessionKDF-AES256 145 | 146 | 0.4.0.127.0.7.1.1.5.2 ecka-dh Anonymous Diffie-Hellman Key Agreement 147 | 148 | 0.4.0.127.0.7.1.1.5.2.1 ecka-dh-X963KDF 149 | 0.4.0.127.0.7.1.1.5.2.1.1 ecka-dh-X963KDF-SHA1 150 | 0.4.0.127.0.7.1.1.5.2.1.2 ecka-dh-X963KDF-SHA224 151 | 0.4.0.127.0.7.1.1.5.2.1.3 ecka-dh-X963KDF-SHA256 152 | 0.4.0.127.0.7.1.1.5.2.1.4 ecka-dh-X963KDF-SHA385 153 | 0.4.0.127.0.7.1.1.5.2.1.5 ecka-dh-X963KDF-SHA512 154 | 0.4.0.127.0.7.1.1.5.2.1.6 ecka-dh-X963KDF-RIPEMD160 155 | 156 | 0.4.0.127.0.7.1.1.5.2.2 ecka-dh-SessionKDF 157 | 0.4.0.127.0.7.1.1.5.2.2.1 ecka-dh-SessionKDF-3DES 158 | 0.4.0.127.0.7.1.1.5.2.2.2 ecka-dh-SessionKDF-AES128 159 | 0.4.0.127.0.7.1.1.5.2.2.3 ecka-dh-SessionKDF-AES192 160 | 0.4.0.127.0.7.1.1.5.2.2.4 ecka-dh-SessionKDF-AES256 161 | 162 | 163 | 1.2.840.10045 ansi-X9-62 ANSI X9.62 root identifier 164 | 1.2.840.10045.2 id-publicKeyType 165 | 1.2.840.10045.2.1 id-ecPublicKey Elliptic curve public key in X9.62 format 166 | 167 | # From http://support.microsoft.com/kb/287547 168 | 1.3.6.1.4.1.311.2.1.4 SPC_INDIRECT_DATA_OBJID 169 | 1.3.6.1.4.1.311.2.1.11 SPC_STATEMENT_TYPE_OBJID 170 | 1.3.6.1.4.1.311.2.1.12 SPC_SP_OPUS_INFO_OBJID 171 | 1.3.6.1.4.1.311.2.1.15 SPC_PE_IMAGE_DATA_OBJID 172 | 1.3.6.1.4.1.311.2.1.21 SPC_INDIVIDUAL_SP_KEY_PURPOSE_OBJID 173 | 174 | # http://www.oid-info.com/get/1.3.14.3.2.29 175 | 1.3.14.3.2.29 sha-1WithRSAEncryption 176 | -------------------------------------------------------------------------------- /parse-usbsnoop.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/henryk/cyberflex-shell/0f7584aab47400ea346c938a3f13b7a099c22f2c/parse-usbsnoop.py -------------------------------------------------------------------------------- /pycsc-0.0.3_new-pcsc.patch: -------------------------------------------------------------------------------- 1 | diff -u pycsc-0.0.3/pycsc.c pycsc-0.0.3_clean/pycsc.c 2 | --- pycsc-0.0.3/pycsc.c 2004-06-21 02:54:29.000000000 +0200 3 | +++ pycsc-0.0.3_clean/pycsc.c 2007-02-10 03:05:38.000000000 +0100 4 | @@ -23,7 +23,7 @@ 5 | #endif 6 | 7 | /* Internal tool */ 8 | -static LONG getReaderList(SCARDCONTEXT hContext, LPSTR* pmszReaders, 9 | +static LONG getReaderList(SCARDCONTEXT hContext, LPTSTR* pmszReaders, 10 | DWORD *pdwReaders); 11 | 12 | #ifdef _WINDOWS_ 13 | @@ -256,14 +256,14 @@ 14 | BYTE pbAtr[MAX_ATR_SIZE]; 15 | DWORD dwAtrLen, dwProt=0, dwState=0; 16 | DWORD dwReaderLen; 17 | - LPSTR pcReaders; 18 | + LPTSTR pcReaders; 19 | LONG rv; 20 | PyObject *ret_value; 21 | 22 | dwReaderLen = 10000; 23 | dwAtrLen = 0; 24 | /* Dry run to get the length of the reader name */ 25 | - rv = SCardStatus( object->hCard, (LPSTR) NULL, &dwReaderLen, 26 | + rv = SCardStatus( object->hCard, (LPTSTR) NULL, &dwReaderLen, 27 | &dwState, &dwProt, NULL, &dwAtrLen ); 28 | 29 | if ( rv != SCARD_S_SUCCESS ) 30 | @@ -340,7 +340,7 @@ 31 | { 32 | pycscobject *object = (pycscobject *)self; 33 | LONG rv; 34 | - unsigned long len; 35 | + int len; 36 | unsigned char *sendBuffer; 37 | DWORD bSendPci; 38 | SCARD_IO_REQUEST *pioSendPci; 39 | @@ -499,8 +499,8 @@ 40 | static PyObject * pycscobject_pycsc(PyObject *self, PyObject * args, PyObject *keywds) 41 | { 42 | /* No reader name in args, connect to the first reader */ 43 | - LPSTR mszReaders = NULL; 44 | - LPSTR szRequestedReader = ""; 45 | + LPTSTR mszReaders = NULL; 46 | + LPTSTR szRequestedReader = ""; 47 | DWORD dwReaders; 48 | DWORD dwMode = SCARD_SHARE_SHARED; 49 | DWORD eProtocol; /* effective protocol */ 50 | @@ -611,8 +611,8 @@ 51 | static PyObject * pycscobject_listReader(PyObject *self, PyObject * args) 52 | { 53 | SCARDCONTEXT hContext; 54 | - LPSTR mszReaders = NULL; 55 | - LPSTR mszReadersScan; 56 | + LPTSTR mszReaders = NULL; 57 | + LPTSTR mszReadersScan; 58 | DWORD dwReaders; 59 | LONG rv; 60 | 61 | @@ -941,10 +941,10 @@ 62 | } 63 | 64 | /* Internal tool */ 65 | -static LONG getReaderList(SCARDCONTEXT hContext, LPSTR* pmszReaders, DWORD *pdwReaders) 66 | +static LONG getReaderList(SCARDCONTEXT hContext, LPTSTR* pmszReaders, DWORD *pdwReaders) 67 | { 68 | - LPCSTR mszGroups = 0; 69 | - LPSTR mszReaders = NULL; 70 | + LPCTSTR mszGroups = 0; 71 | + LPTSTR mszReaders = NULL; 72 | LONG dwReaders; 73 | LONG rv; 74 | 75 | diff -u pycsc-0.0.3/setup.py pycsc-0.0.3_clean/setup.py 76 | --- pycsc-0.0.3/setup.py 2004-01-19 17:09:32.000000000 +0100 77 | +++ pycsc-0.0.3_clean/setup.py 2007-02-10 03:05:25.000000000 +0100 78 | @@ -31,7 +31,7 @@ 79 | include = [] 80 | else: 81 | libs = ["pcsclite"] 82 | - include = ["/usr/include/pcsc"] 83 | + include = ["/usr/include/PCSC"] 84 | 85 | 86 | setup(name="pycsc", version="0.3", 87 | -------------------------------------------------------------------------------- /readers.py: -------------------------------------------------------------------------------- 1 | try: 2 | import smartcard, smartcard.CardRequest 3 | except ImportError: 4 | print >>sys.stderr, """Could not import smartcard module. Please install pyscard 5 | from http://pyscard.sourceforge.net/ 6 | If you can't install pyscard and want to continue using 7 | pycsc you'll need to downgrade to SVN revision 246. 8 | """ 9 | raise 10 | 11 | import sys, utils, getopt, binascii, time 12 | 13 | class Smartcard_Reader(object): 14 | def list_readers(cls): 15 | "Return a list of tuples: (reader name, implementing object)" 16 | return [] 17 | list_readers = classmethod(list_readers) 18 | 19 | 20 | _CONNECT_NO_CARD = object() 21 | _CONNECT_MUTE_CARD = object() 22 | _CONNECT_DONE = object() 23 | def _internal_connect(self): 24 | """Must implement the iterator protocol and yield 25 | one of self._CONNECT_NO_CARD, self._CONNECT_MUTE_CARD or self._CONNECT_DONE. 26 | The iterator will not be called again after yielding _CONNECT_DONE, so it must 27 | clean itself up before that.""" 28 | raise NotImplementedError, "Please implement in a sub-class" 29 | 30 | def connect(self): 31 | have_card = False 32 | printed = False 33 | for result in self._internal_connect(): 34 | if result is self._CONNECT_DONE: 35 | have_card = True 36 | break 37 | elif result is self._CONNECT_MUTE_CARD: 38 | print "Card is mute or absent. Please retry." 39 | elif result is self._CONNECT_NO_CARD: 40 | if not printed: 41 | print "Please insert card ..." 42 | printed = True 43 | return have_card 44 | 45 | def get_ATR(self): 46 | "Get the ATR of the inserted card as a binary string" 47 | raise NotImplementedError, "Please implement in a sub-class" 48 | 49 | def transceive(self, data): 50 | "Send a binary blob, receive a binary blob" 51 | raise NotImplementedError, "Please implement in a sub-class" 52 | 53 | def disconnect(self): 54 | "Disconnect from the card and release all resources" 55 | raise NotImplementedError, "Please implement in a sub-class" 56 | 57 | class PCSC_Reader(Smartcard_Reader): 58 | def __init__(self, reader): 59 | self._reader = reader 60 | self._name = str(reader) 61 | self._cardservice = None 62 | 63 | name = property(lambda self: self._name, None, None, "The human readable name of the reader") 64 | 65 | def list_readers(cls): 66 | try: 67 | return [ (str(r), cls(r)) for r in smartcard.System.readers() ] 68 | except smartcard.pcsc.PCSCExceptions.EstablishContextException: 69 | return [] 70 | list_readers = classmethod(list_readers) 71 | 72 | def _internal_connect(self): 73 | unpatched = False 74 | while True: 75 | try: 76 | if not unpatched: 77 | cardrequest = smartcard.CardRequest.CardRequest( readers=[self._reader], timeout=0.1 ) 78 | else: 79 | cardrequest = smartcard.CardRequest.CardRequest( readers=[self._reader], timeout=1 ) 80 | 81 | self._cardservice = cardrequest.waitforcard() 82 | self._cardservice.connection.connect() 83 | del cardrequest 84 | yield self._CONNECT_DONE 85 | except TypeError: 86 | unpatched = True 87 | except (KeyboardInterrupt, SystemExit): 88 | raise 89 | except smartcard.Exceptions.CardRequestException: 90 | if sys.exc_info()[1].message.endswith("Command timeout."): 91 | yield self._CONNECT_NO_CARD 92 | else: 93 | raise 94 | except smartcard.Exceptions.CardRequestTimeoutException: 95 | yield self._CONNECT_NO_CARD 96 | except smartcard.Exceptions.NoCardException: 97 | yield self._CONNECT_MUTE_CARD 98 | except smartcard.Exceptions.CardConnectionException: 99 | yield self._CONNECT_MUTE_CARD 100 | 101 | def get_ATR(self): 102 | return smartcard.util.toASCIIString(self._cardservice.connection.getATR()) 103 | 104 | def get_protocol(self): 105 | hresult, reader, state, protocol, atr = smartcard.scard.SCardStatus( self._cardservice.connection.component.hcard ) 106 | return ((protocol == smartcard.scard.SCARD_PROTOCOL_T0) and (0,) or (1,))[0] 107 | 108 | PROTOMAP = { 109 | 0: smartcard.scard.SCARD_PCI_T0, 110 | 1: smartcard.scard.SCARD_PCI_T1, 111 | } 112 | 113 | def transceive(self, data): 114 | data_bytes = map(lambda x: ord(x), data) 115 | data, sw1, sw2 = self._cardservice.connection.transmit(data_bytes, protocol=self.PROTOMAP[self.get_protocol()]) 116 | result_binary = map(lambda x: chr(x), data + [sw1,sw2]) 117 | return result_binary 118 | 119 | def disconnect(self): 120 | self._cardservice.connection.disconnect() 121 | del self._cardservice 122 | self._cardservice = None 123 | 124 | class ACR122_Reader(Smartcard_Reader): 125 | """This class implements ISO 14443-4 access through the 126 | PN532 in an ACR122 reader with firmware version 1.x""" 127 | def list_readers(cls): 128 | pcsc_readers = PCSC_Reader.list_readers() 129 | readers = [] 130 | for name, obj in pcsc_readers: 131 | if name.startswith("ACS ACR 38U-CCID") or name.startswith("ACS ACR122U PICC Interface"): 132 | reader = cls(obj) 133 | readers.append( (reader.name, reader) ) 134 | return readers 135 | list_readers = classmethod(list_readers) 136 | 137 | name = property(lambda self: self._name, None, None, "The human readable name of the reader") 138 | 139 | def __init__(self, parent): 140 | self._parent = parent 141 | self._name = self._parent.name+"-RFID" 142 | self._current_target = None 143 | self._current_target_number = 0 144 | 145 | def pn532_transceive_raw(self, command): 146 | c_apdu = "\xff\x00\x00\x00" + chr(len(command)) + command 147 | r_apdu = self._parent.transceive(c_apdu) 148 | 149 | if len(r_apdu) == 2 and r_apdu[0] == "\x61": 150 | c_apdu = "\xff\xc0\x00\x00" + r_apdu[1] 151 | r_apdu = self._parent.transceive(c_apdu) 152 | 153 | return r_apdu 154 | 155 | def pn532_transceive(self, command): 156 | response = self.pn532_transceive_raw(command) 157 | 158 | if len(response) < 2 or response[-2:] != ["\x90", "\x00"]: 159 | raise IOError, "Couldn't communicate with PN532" 160 | 161 | if not (response[0] == "\xd5" and ord(response[1]) == ord(command[1])+1 ): 162 | raise IOError, "Wrong response from PN532" 163 | 164 | return "".join(response[:-2]) 165 | 166 | def pn532_acquire_card(self): 167 | # Turn antenna power off and on to forcefully reinitialize the card 168 | self.pn532_transceive("\xd4\x32\x01\x00") 169 | self.pn532_transceive("\xd4\x32\x01\x01") 170 | 171 | self._last_ats = [] 172 | 173 | response = self.pn532_transceive("\xd4\x4a\x01\x00") 174 | r = utils.PN532_Frame(response) 175 | r.parse_result(0) 176 | if len(r.targets) > 0: 177 | self._current_target_number, self._current_target = r.targets.items()[0] 178 | return True 179 | else: 180 | response = self.pn532_transceive("\xd4\x4a\x01\x03\x00") 181 | r = utils.PN532_Frame(response) 182 | r.parse_result(3) 183 | if len(r.targets) > 0: 184 | self._current_target_number, self._current_target = r.targets.items()[0] 185 | return True 186 | 187 | def _internal_connect(self): 188 | self._parent.connect() 189 | self.pn532_transceive("\xd4\x32\x05\x00\x00\x00") 190 | while True: 191 | if self.pn532_acquire_card(): 192 | yield self._CONNECT_DONE 193 | else: 194 | yield self._CONNECT_NO_CARD 195 | time.sleep(1) 196 | 197 | @staticmethod 198 | def _extract_historical_bytes_from_ats(ats): 199 | hist_bytes = [] 200 | if ats[0] > 1: 201 | interface_bytes = 0 202 | if ats[1] & 0x40: interface_bytes = interface_bytes + 1 203 | if ats[1] & 0x20: interface_bytes = interface_bytes + 1 204 | if ats[1] & 0x10: interface_bytes = interface_bytes + 1 205 | hist_bytes = ats[ (2+interface_bytes): ] 206 | return hist_bytes 207 | 208 | def get_ATR(self): 209 | # FIXME Properly implement for PC/SC version 2 210 | if self._current_target is None: return "" 211 | 212 | hist_bytes = [] 213 | 214 | if self._current_target.type == utils.PN532_Target.TYPE_ISO14443A: 215 | if len(self._current_target.ats) > 0: 216 | # Quick and dirty: Extract historical bytes from ATS 217 | ats = self._current_target.ats 218 | hist_bytes = self._extract_historical_bytes_from_ats(ats) 219 | if len(hist_bytes) > 15: hist_bytes = hist_bytes[:15] 220 | else: 221 | return "\x3b\x80\x80\x01\x01" 222 | elif self._current_target.type == utils.PN532_Target.TYPE_ISO14443B: 223 | hist_bytes = self._current_target.atqb[5:12] 224 | if len(self._current_target.attrib_res) > 0: 225 | hist_bytes.append(self._current_target.attrib_res[0] & 0xf0) 226 | else: 227 | hist_bytes.append(0) 228 | else: 229 | return "" 230 | 231 | # The ISO 14443-4 A or -3 B code paths should have filled the hist_bytes list 232 | atr = [0x3b, 0x80 + len(hist_bytes), 0x80, 0x01] + hist_bytes 233 | atr.append( reduce(lambda a,b: a ^ b, atr) ^ 0x3b ) 234 | return "".join(map(chr, atr)) 235 | 236 | 237 | def transceive(self, data): 238 | try: 239 | command = utils.C_APDU(data) 240 | result = [] 241 | error = None 242 | response = None 243 | 244 | if command.cla == 0xff and command.ins == 0xca: 245 | if command.p1 == 0x00: 246 | # Get UID/PUPI 247 | if self._current_target is None: 248 | error = "\x6a\x81" 249 | elif self._current_target.type == utils.PN532_Target.TYPE_ISO14443A: 250 | result = self._current_target.nfcid 251 | elif self._current_target.type == utils.PN532_Target.TYPE_ISO14443B: 252 | result = self._current_target.atqb[1:5] 253 | else: 254 | error = "\x6a\x81" 255 | 256 | elif command.p1 == 0x01: 257 | # Get ATS historical bytes 258 | if self._current_target is None: 259 | error = "\x6a\x81" 260 | elif self._current_target.type == utils.PN532_Target.TYPE_ISO14443A: 261 | ats = self._current_target.ats 262 | result = self._extract_historical_bytes_from_ats(ats) 263 | else: 264 | error = "\x6a\x81" 265 | 266 | else: 267 | error = "\x6a\x81" 268 | 269 | if error is not None: 270 | response = utils.R_APDU(error) 271 | else: 272 | if command.le is 0 or command.le == len(result): 273 | response = utils.R_APDU(data=result, sw1=0x90, sw2=0) 274 | elif command.le < len(result): 275 | response = utils.R_APDU(sw1=0x6c, sw2=len(result)) 276 | elif command.le > len(result): 277 | response = utils.R_APDU(data=result + [0] * (command.le-len(result)), 278 | sw1=0x62, sw2=0x82) 279 | 280 | if response is not None: 281 | return response.render() 282 | except: 283 | # Just go on and try to process the data normally 284 | pass 285 | 286 | response = self.pn532_transceive("\xd4\x40" + chr(self._current_target_number) + data) 287 | if response[2] != "\x00": 288 | # FIXME Proper error processing 289 | raise IOError, "Error while transceiving" 290 | return response[3:] 291 | 292 | def disconnect(self): 293 | self._parent.disconnect() 294 | 295 | def list_readers(): 296 | "Collect readers from all known drivers" 297 | readers = PCSC_Reader.list_readers() 298 | readers.extend( ACR122_Reader.list_readers() ) 299 | return readers 300 | 301 | def connect_to(reader): 302 | "Open the connection to a reader" 303 | 304 | readerObject = None 305 | readers = list_readers() 306 | 307 | if isinstance(reader, int) or reader.isdigit(): 308 | reader = int(reader) 309 | readerObject = readers[reader][1] 310 | else: 311 | for i, name, obj in readers: 312 | if str(name).startswith(reader): 313 | readerObject = obj 314 | 315 | if readerObject is None: 316 | readerObject = readers[0][1] 317 | 318 | print "Using reader: %s" % readerObject.name 319 | 320 | readerObject.connect() 321 | 322 | print "ATR: %s" % utils.hexdump(readerObject.get_ATR(), short = True) 323 | return readerObject 324 | 325 | class CommandLineArgumentHelper: 326 | OPTIONS = "r:l" 327 | LONG_OPTIONS = ["reader=", "list-readers"] 328 | exit_now = False 329 | reader = None 330 | 331 | def connect(self): 332 | "Open the connection to a card" 333 | 334 | if self.reader is None: 335 | self.reader = 0 336 | 337 | return connect_to(self.reader) 338 | 339 | def getopt(self, argv, opts="", long_opts=[]): 340 | "Wrapper around getopt.gnu_getopt. Handles common arguments, returns everything else." 341 | (options, arguments) = getopt.gnu_getopt(sys.argv[1:], self.OPTIONS+opts, self.LONG_OPTIONS+long_opts) 342 | 343 | unrecognized = [] 344 | 345 | for (option, value) in options: 346 | if option in ("-r","--reader"): 347 | self.reader = value 348 | elif option in ("-l","--list-readers"): 349 | for i, (name, obj) in enumerate(list_readers()): 350 | print "%i: %s" % (i,name) 351 | self.exit_now = True 352 | else: 353 | unrecognized.append( (option, value) ) 354 | 355 | if self.exit_now: 356 | sys.exit() 357 | 358 | return unrecognized, arguments 359 | 360 | 361 | if __name__ == "__main__": 362 | list_readers() 363 | 364 | -------------------------------------------------------------------------------- /readpass.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | import utils, cards, TLV_utils, sys, binascii, time, traceback, readers 5 | 6 | OPTIONS = "iGW:R:" 7 | LONG_OPTIONS = ["interactive","no-gui", "write-files-basename", "read-files-basename"] 8 | 9 | use_gui = True 10 | write_files = None 11 | read_files = None 12 | start_interactive = False 13 | 14 | if __name__ == "__main__": 15 | c = readers.CommandLineArgumentHelper() 16 | 17 | (options, arguments) = c.getopt(sys.argv[1:], OPTIONS, LONG_OPTIONS) 18 | 19 | for option, value in options: 20 | if option in ("-G","--no-gui"): 21 | use_gui = False 22 | start_interactive = False 23 | elif option in ("-W","--write-files-basename"): 24 | write_files = value 25 | elif option in ("-R","--read-files-basename"): 26 | read_files = value 27 | elif option in ("-i", "--interactive"): 28 | start_interactive = True 29 | use_gui = True 30 | 31 | if read_files is None and not start_interactive: 32 | card_object = c.connect() 33 | card = cards.new_card_object(card_object) 34 | cards.generic_card.DEBUG = False 35 | 36 | print >>sys.stderr, "Using %s" % card.DRIVER_NAME 37 | 38 | if len(arguments) > 1: 39 | p = cards.passport_application.Passport.from_card(card, arguments[:2]) 40 | elif len(arguments) == 1: 41 | p = cards.passport_application.Passport.from_card(card, ["",arguments[0]]) 42 | else: 43 | p = cards.passport_application.Passport.from_card(card) 44 | elif read_files is not None: 45 | p = cards.passport_application.Passport.from_files(basename=read_files) 46 | elif start_interactive: 47 | p = None 48 | 49 | if write_files is not None and not start_interactive: 50 | p.to_files(basename=write_files) 51 | 52 | if use_gui: 53 | import gui 54 | 55 | g = gui.PassportGUI() 56 | if p is not None: 57 | g.set_passport(p) 58 | else: 59 | g.clear_display() 60 | g.set_card_factory(c) 61 | g.run() 62 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from utilstest import * 2 | -------------------------------------------------------------------------------- /tests/__main__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tests import * 3 | 4 | if __name__ == "__main__": 5 | unittest.main() 6 | -------------------------------------------------------------------------------- /tests/utilstest.py: -------------------------------------------------------------------------------- 1 | """Unit test for utils.py""" 2 | 3 | import utils 4 | import unittest 5 | 6 | class APDUCase1Tests(unittest.TestCase): 7 | 8 | def setUp(self): 9 | self.a4 = utils.C_APDU("\x00\xa4\x00\x00") 10 | 11 | def tearDown(self): 12 | del self.a4 13 | 14 | def testCreation(self): 15 | self.assertEqual(0, self.a4.CLA) 16 | self.assertEqual(0xa4, self.a4.INS) 17 | self.assertEqual(0, self.a4.P1) 18 | self.assertEqual(0, self.a4.P2) 19 | 20 | def testRender(self): 21 | self.assertEqual("\x00\xa4\x00\x00", self.a4.render()) 22 | 23 | def testCopy(self): 24 | b0 = utils.C_APDU(self.a4, INS=0xb0) 25 | 26 | self.assertEqual("\x00\xb0\x00\x00", b0.render()) 27 | 28 | def testAssign(self): 29 | self.a4.p2 = 5 30 | 31 | self.assertEqual(5, self.a4.P2) 32 | self.assertEqual("\x00\xa4\x00\x05", self.a4.render()) 33 | 34 | def testCreateSequence(self): 35 | a4_2 = utils.C_APDU(0, 0xa4, 0, 0) 36 | 37 | self.assertEqual(self.a4.render(), a4_2.render()) 38 | 39 | class APDUChainTests(unittest.TestCase): 40 | 41 | def testChain(self): 42 | a = utils.R_APDU("abcd\x61\x04") 43 | b = utils.R_APDU("efgh\x90\x00") 44 | 45 | c = a.append(b) 46 | 47 | self.assertEqual("abcdefgh\x90\x00", c.render()) 48 | -------------------------------------------------------------------------------- /tlvdecoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: iso-8859-1 -*- 3 | 4 | from TLV_utils import * 5 | import binascii, sys 6 | 7 | if __name__ == "__main__": 8 | a = binascii.unhexlify("".join( sys.stdin.read().split() )) 9 | print decode(a) 10 | --------------------------------------------------------------------------------