diff options
author | Henryk Plötz <henryk@ploetzli.ch> | 2010-10-15 17:11:39 +0200 |
---|---|---|
committer | Henryk Plötz <henryk@ploetzli.ch> | 2010-10-15 17:11:39 +0200 |
commit | 367b41a0616b96a96a4b614fb1c9c2ff364df6a9 (patch) | |
tree | f89befdb02d452f554a55df3bbd6d6d29f53bc70 | |
parent | 06a899f8e625ac423ce642fee2e27943a0cd8360 (diff) | |
parent | c53914f1aedf737ea9bd225bfd9ce62ebd9903c8 (diff) |
Merge branch 'desfire-native'
-rw-r--r-- | cards/generic_card.py | 138 | ||||
-rw-r--r-- | cards/gsm_card.py | 9 | ||||
-rw-r--r-- | cards/iso_7816_4_card.py | 12 | ||||
-rw-r--r-- | cards/iso_card.py | 145 | ||||
-rw-r--r-- | cards/java_card.py | 7 | ||||
-rw-r--r-- | cards/passport_application.py | 3 | ||||
-rw-r--r-- | cards/pn532_card.py | 8 | ||||
-rw-r--r-- | cards/postcard_card.py | 3 | ||||
-rw-r--r-- | cards/rfid_card.py | 6 | ||||
-rw-r--r-- | cards/starcos_card.py | 2 | ||||
-rw-r--r-- | utils.py | 59 |
11 files changed, 232 insertions, 160 deletions
diff --git a/cards/generic_card.py b/cards/generic_card.py index b45f56c..93c5374 100644 --- a/cards/generic_card.py +++ b/cards/generic_card.py @@ -5,26 +5,21 @@ from utils import C_APDU, R_APDU DEBUG = True #DEBUG = False -## Constants for check_sw() -PURPOSE_SUCCESS = 1 # Command executed successful -PURPOSE_GET_RESPONSE = 2 # Command executed successful but needs GET RESPONSE with correct length -PURPOSE_SM_OK = 3 # Command not executed successful or with warnings, but response still contains SM objects -PURPOSE_RETRY = 4 # Command would be executed successful but needs retry with correct length _GENERIC_NAME = "Generic" class Card: DRIVER_NAME = [_GENERIC_NAME] - APDU_GET_RESPONSE = C_APDU(ins=0xc0) - APDU_VERIFY_PIN = C_APDU(ins=0x20) - PURPOSE_SUCCESS, PURPOSE_GET_RESPONSE, PURPOSE_SM_OK, PURPOSE_RETRY = PURPOSE_SUCCESS, PURPOSE_GET_RESPONSE, PURPOSE_SM_OK, PURPOSE_RETRY + COMMAND_GET_RESPONSE = None + + ## Constants for check_sw() + PURPOSE_SUCCESS = 1 # Command executed successful + PURPOSE_GET_RESPONSE = 2 # Command executed successful but needs GET RESPONSE with correct length + PURPOSE_SM_OK = 3 # Command not executed successful or with warnings, but response still contains SM objects + PURPOSE_RETRY = 4 # Command would be executed successful but needs retry with correct length + ## Map for check_sw() - STATUS_MAP = { - PURPOSE_SUCCESS: ("\x90\x00", ), - PURPOSE_GET_RESPONSE: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2 - PURPOSE_SM_OK: ("\x90\x00",), - PURPOSE_RETRY: (), ## Theoretically this would contain "6C??", but I dare not automatically resending a command for _all_ card types - ## Instead, card types for which this is safe should set it in their own STATUS_MAP - } + STATUS_MAP = {} + ## Note: an item in this list must be a tuple of (atr, mask) where atr is a binary ## string and mask a binary mask. Alternatively mask may be None, then ATR must be a regex ## to match on the ATRs hexlify representation @@ -32,18 +27,15 @@ class Card: ## Note: A list of _not_ supported ATRs, overriding any possible match in ATRS. Matching ## is done as for ATRS. STOP_ATRS = [] - ## Note: a key in this dictionary may either be a two-byte string containing - ## a binary status word, or a four-byte string containing a hexadecimal + + ## Note: a key in this dictionary may either be a one- or two-byte string containing + ## a binary status word, or a two or four-byte string containing a hexadecimal ## status word, possibly with ? characters marking variable nibbles. - ## Hexadecimal characters MUST be in uppercase. The values that four-byte + ## Hexadecimal characters MUST be in uppercase. The values that two- or four-byte ## strings map to may be either format strings, that can make use of the ## keyword substitutions for SW1 and SW2 or a callable accepting two arguments ## (SW1, SW2) that returns a string. STATUS_WORDS = { - '\x90\x00': "Normal execution", - '61??': "%(SW2)i (0x%(SW2)02x) bytes of response data can be retrieved with GetResponse.", - '6C??': "Bad value for LE, 0x%(SW2)02x is the correct value.", - '63C?': lambda SW1,SW2: "The counter has reached the value '%i'" % (SW2%16) } ## For the format of this dictionary of dictionaries see TLV_utils.tags TLV_OBJECTS = {} @@ -51,44 +43,10 @@ class Card: ## Format: "AID (binary)": ("name", [optional: description, {more information}]) APPLICATIONS = { - "\xa0\x00\x00\x01\x67\x45\x53\x49\x47\x4e": ("DF.ESIGN", ), - "\xa0\x00\x00\x00\x63\x50\x4b\x43\x53\x2d\x31\x35": ("DF_PKCS15", ), - "\xD2\x76\x00\x01\x24\x01": ("DF_OpenPGP", "OpenPGP card", {"significant_length": 6} ), - "\xa0\x00\x00\x02\x47\x10\x01": ("DF_LDS", "Machine Readable Travel Document", {"alias": ("mrtd",)}), - ## The following are from 0341a.pdf: BSI-DSZ-CC-0341-2006 - "\xD2\x76\x00\x00\x66\x01": ("DF_SIG", "Signature application", {"fid": "\xAB\x00"}), - "\xD2\x76\x00\x00\x25\x5A\x41\x02\x00": ("ZA_MF_NEU", "Zusatzanwendungen", {"fid": "\xA7\x00"}), - "\xD2\x76\x00\x00\x25\x45\x43\x02\x00": ("DF_EC_CASH_NEU", "ec-Cash", {"fid": "\xA1\x00"}), - "\xD2\x76\x00\x00\x25\x45\x50\x02\x00": ("DF_BOERSE_NEU", "Geldkarte", {"fid": "\xA2\x00", "alias": ("geldkarte",)}), - "\xD2\x76\x00\x00\x25\x47\x41\x01\x00": ("DF_GA_MAESTRO", "GA-Maestro", {"fid": "\xAC\x00"}), - "\xD2\x76\x00\x00\x25\x54\x44\x01\x00": ("DF_TAN", "TAN-Anwendung", {"fid": "\xAC\x02"}), - "\xD2\x76\x00\x00\x25\x4D\x01\x02\x00": ("DF_MARKTPLATZ_NEU", "Marktplatz", {"fid": "\xB0\x01"}), - "\xD2\x76\x00\x00\x25\x46\x53\x02\x00": ("DF_FAHRSCHEIN_NEU", "Fahrschein", {"fid": "\xB0\x00"}), - "\xD2\x76\x00\x00\x25\x48\x42\x02\x00": ("DF_BANKING_20" , "HBCI", {"fid": "\xA6\x00"}), - "\xD2\x76\x00\x00\x25\x4E\x50\x01\x00": ("DF_NOTEPAD", "Notepad", {"fid": "\xA6\x10"}), - - "\xd2\x76\x00\x00\x85\x01\x00": ("NFC_TYPE_4", "NFC NDEF Application on tag type 4", {"alias": ("nfc",)}, ), - - # From TR-03110_v201_pdf.pdf - "\xE8\x07\x04\x00\x7f\x00\x07\x03\x02": ("DF_eID", "eID application"), - - "\xd2\x76\x00\x00\x25\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00", "alias": ("vrs",)}, ), - "\xd2\x76\x00\x01\x35\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00",}, ), } - # Alias for DF_BOERSE_NEU - APPLICATIONS["\xA0\x00\x00\x00\x59\x50\x41\x43\x45\x01\x00"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x45\x50\x02\x00"] - # Alias for DF_GA_MAESTRO - APPLICATIONS["\xA0\x00\x00\x00\x04\x30\x60"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x47\x41\x01\x00"] ## Format: "RID (binary)": ("vendor name", [optional: {more information}]) VENDORS = { - "\xD2\x76\x00\x01\x24": ("Free Software Foundation Europe", ), - "\xD2\x76\x00\x00\x25": ("Bankenverlag", ), - "\xD2\x76\x00\x00\x60": ("Wolfgang Rankl", ), - "\xD2\x76\x00\x00\x05": ("Giesecke & Devrient", ), - "\xD2\x76\x00\x00\x40": ("Zentralinstitut fuer die Kassenaerztliche Versorgung in der Bundesrepublik Deutschland", ), # hpc-use-cases-01.pdf - "\xa0\x00\x00\x02\x47": ("ICAO", ), - "\xa0\x00\x00\x03\x06": ("PC/SC Workgroup", ), } def _decode_df_name(self, value): @@ -125,44 +83,15 @@ class Card: # Static method for when there is no object reference return Card._decode_df_name(value) - TLV_OBJECTS[TLV_utils.context_FCP] = { - 0x84: (decode_df_name, "DF name"), - } - TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] - def __init__(self, reader): self.reader = reader self._i = 0 self.last_apdu = None - self.last_sw = None self.last_result = None - self.sw_changed = False self._last_start = None self.last_delta = None - def post_merge(self): - ## Called after cards.__init__.Cardmultiplexer._merge_attributes - self.TLV_OBJECTS[TLV_utils.context_FCP][0x84] = (self._decode_df_name, "DF name") - self.TLV_OBJECTS[TLV_utils.context_FCI][0x84] = (self._decode_df_name, "DF name") - - def verify_pin(self, pin_number, pin_value): - apdu = C_APDU(self.APDU_VERIFY_PIN, P2 = pin_number, - data = pin_value) - result = self.send_apdu(apdu) - return self.check_sw(result.sw) - - def cmd_verify(self, pin_number, pin_value): - """Verify a PIN.""" - pin_number = int(pin_number, 0) - pin_value = binascii.a2b_hex("".join(pin_value.split())) - self.verify_pin(pin_number, pin_value) - - def cmd_reset(self): - """Reset the card.""" - # FIXME - raise NotImplementedException - def cmd_parsetlv(self, start = None, end = None): "Decode the TLV data in the last response, start and end are optional" lastlen = len(self.last_result.data) @@ -189,9 +118,13 @@ class Card: "description": len(info) > 1 and info[1] or "" } + def cmd_reset(self): + """Reset the card.""" + # FIXME + raise NotImplementedException + COMMANDS = { "reset": cmd_reset, - "verify": cmd_verify, "parse_tlv": cmd_parsetlv, "show_applications": cmd_show_applications, } @@ -203,11 +136,9 @@ class Card: print ">> " + utils.hexdump(apdu_binary, indent = 3) result_binary = self.reader.transceive(apdu_binary) - result = R_APDU(result_binary) + result = apdu.RESPONSE_CLASS(result_binary) self.last_apdu = apdu - self.last_sw = result.sw - self.sw_changed = True if DEBUG: print "<< " + utils.hexdump(result_binary, indent = 3) @@ -216,11 +147,11 @@ class Card: def _send_with_retry(self, apdu): result = self._real_send(apdu) - if self.check_sw(result.sw, PURPOSE_GET_RESPONSE): + if self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE): ## Need to call GetResponse - gr_apdu = C_APDU(self.APDU_GET_RESPONSE, le = result.sw2, cla=apdu.cla) # FIXME + gr_apdu = C_APDU(self.COMMAND_GET_RESPONSE, le = result.sw2, cla=apdu.cla) # FIXME result = R_APDU(self._real_send(gr_apdu)) - elif self.check_sw(result.sw, PURPOSE_RETRY) and apdu.Le == 0: + elif self.check_sw(result.sw, self.PURPOSE_RETRY) and apdu.Le == 0: ## Retry with correct Le gr_apdu = C_APDU(apdu, le = result.sw2) result = R_APDU(self._real_send(gr_apdu)) @@ -303,29 +234,6 @@ class Card: return None match_statusword = staticmethod(match_statusword) - def decode_statusword(self): - if self.last_sw is None: - return "No command executed so far" - else: - retval = None - - matched_sw = self.match_statusword(self.STATUS_WORDS.keys(), self.last_sw) - if matched_sw is not None: - retval = self.STATUS_WORDS.get(matched_sw) - if isinstance(retval, str): - retval = retval % { "SW1": ord(self.last_sw[0]), - "SW2": ord(self.last_sw[1]) } - - elif callable(retval): - retval = retval( ord(self.last_sw[0]), - ord(self.last_sw[1]) ) - - if retval is None: - return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw) - else: - return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw)) - - def get_driver_name(self): if len(self.DRIVER_NAME) > 1: names = [e for e in self.DRIVER_NAME if e != _GENERIC_NAME] diff --git a/cards/gsm_card.py b/cards/gsm_card.py index 815bdac..e00d1a1 100644 --- a/cards/gsm_card.py +++ b/cards/gsm_card.py @@ -1,11 +1,12 @@ import utils -from generic_card import * +from iso_card import * -class GSM_Card(Card): +class GSM_Card(ISO_Card): DRIVER_NAME = ["GSM"] - APDU_GET_RESPONSE = C_APDU("\xa0\xC0\x00\x00") + COMMAND_GET_RESPONSE = C_APDU("\xa0\xC0\x00\x00") + STATUS_MAP = { - PURPOSE_GET_RESPONSE: ("9F??", ) + Card.PURPOSE_GET_RESPONSE: ("9F??", ) } ATRS = [ diff --git a/cards/iso_7816_4_card.py b/cards/iso_7816_4_card.py index c13c100..899dcb2 100644 --- a/cards/iso_7816_4_card.py +++ b/cards/iso_7816_4_card.py @@ -1,6 +1,6 @@ import sys;sys.path.append(".."); sys.path.append(".") import TLV_utils -from generic_card import * +from iso_card import * from generic_application import Application import building_blocks @@ -156,7 +156,7 @@ class iso_df(iso_node): if node not in self._children: self._children.append(node) -class ISO_7816_4_Card(building_blocks.Card_with_read_binary,Card): +class ISO_7816_4_Card(building_blocks.Card_with_read_binary,ISO_Card): APDU_SELECT_APPLICATION = C_APDU(ins=0xa4,p1=0x04) APDU_SELECT_FILE = C_APDU(ins=0xa4, le=0) APDU_READ_BINARY = C_APDU(ins=0xb0,le=0) @@ -284,18 +284,18 @@ class ISO_7816_4_Card(building_blocks.Card_with_read_binary,Card): aid = self.resolve_symbolic_aid(application) Application.load_applications(self, aid) - ATRS = list(Card.ATRS) + ATRS = list(ISO_Card.ATRS) ATRS.extend( [ (".*", None), ## For now we accept any card ] ) - STOP_ATRS = list(Card.STOP_ATRS) + STOP_ATRS = list(ISO_Card.STOP_ATRS) STOP_ATRS.extend( [ ("3b8f8001804f0ca000000306......00000000..", None), # Contactless storage cards (PC/SC spec part 3 section 3.1.3.2.3 ("3b8180018080", None), # Mifare DESfire (special case of contactless smartcard, ibid.) ] ) - COMMANDS = dict(Card.COMMANDS) + COMMANDS = dict(ISO_Card.COMMANDS) COMMANDS.update(building_blocks.Card_with_read_binary.COMMANDS) COMMANDS.update( { "select_application": cmd_selectapplication, @@ -307,7 +307,7 @@ class ISO_7816_4_Card(building_blocks.Card_with_read_binary,Card): "next_record": cmd_next_record, } ) - STATUS_WORDS = dict(Card.STATUS_WORDS) + STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) STATUS_WORDS.update( { "62??": "Warning, State of non-volatile memory unchanged", "63??": "Warning, State of non-volatile memory changed", diff --git a/cards/iso_card.py b/cards/iso_card.py new file mode 100644 index 0000000..e4cca21 --- /dev/null +++ b/cards/iso_card.py @@ -0,0 +1,145 @@ +import smartcard +import TLV_utils, crypto_utils, utils, binascii, fnmatch, re, time +from generic_card import Card +from utils import C_APDU, R_APDU + +class ISO_Card(Card): + DRIVER_NAME = ["ISO"] + COMMAND_GET_RESPONSE = C_APDU(ins=0xc0) + + APDU_VERIFY_PIN = C_APDU(ins=0x20) + + ## Map for check_sw() + STATUS_MAP = { + Card.PURPOSE_SUCCESS: ("\x90\x00", ), + Card.PURPOSE_GET_RESPONSE: ("61??", ), ## If this is received then GET RESPONSE should be called with SW2 + Card.PURPOSE_SM_OK: ("\x90\x00",), + Card.PURPOSE_RETRY: (), ## Theoretically this would contain "6C??", but I dare not automatically resending a command for _all_ card types + ## Instead, card types for which this is safe should set it in their own STATUS_MAP + } + + ATRS = list(Card.ATRS) + STOP_ATRS = list(Card.STOP_ATRS) + + ## Note: a key in this dictionary may either be a one- or two-byte string containing + ## a binary status word, or a two or four-byte string containing a hexadecimal + ## status word, possibly with ? characters marking variable nibbles. + ## Hexadecimal characters MUST be in uppercase. The values that two- or four-byte + ## strings map to may be either format strings, that can make use of the + ## keyword substitutions for SW1 and SW2 or a callable accepting two arguments + ## (SW1, SW2) that returns a string. + STATUS_WORDS = { + '\x90\x00': "Normal execution", + '61??': "%(SW2)i (0x%(SW2)02x) bytes of response data can be retrieved with GetResponse.", + '6C??': "Bad value for LE, 0x%(SW2)02x is the correct value.", + '63C?': lambda SW1,SW2: "The counter has reached the value '%i'" % (SW2%16) + } + ## For the format of this dictionary of dictionaries see TLV_utils.tags + TLV_OBJECTS = dict(Card.TLV_OBJECTS) + DEFAULT_CONTEXT = None + + ## Format: "AID (binary)": ("name", [optional: description, {more information}]) + APPLICATIONS = { + "\xa0\x00\x00\x01\x67\x45\x53\x49\x47\x4e": ("DF.ESIGN", ), + "\xa0\x00\x00\x00\x63\x50\x4b\x43\x53\x2d\x31\x35": ("DF_PKCS15", ), + "\xD2\x76\x00\x01\x24\x01": ("DF_OpenPGP", "OpenPGP card", {"significant_length": 6} ), + "\xa0\x00\x00\x02\x47\x10\x01": ("DF_LDS", "Machine Readable Travel Document", {"alias": ("mrtd",)}), + ## The following are from 0341a.pdf: BSI-DSZ-CC-0341-2006 + "\xD2\x76\x00\x00\x66\x01": ("DF_SIG", "Signature application", {"fid": "\xAB\x00"}), + "\xD2\x76\x00\x00\x25\x5A\x41\x02\x00": ("ZA_MF_NEU", "Zusatzanwendungen", {"fid": "\xA7\x00"}), + "\xD2\x76\x00\x00\x25\x45\x43\x02\x00": ("DF_EC_CASH_NEU", "ec-Cash", {"fid": "\xA1\x00"}), + "\xD2\x76\x00\x00\x25\x45\x50\x02\x00": ("DF_BOERSE_NEU", "Geldkarte", {"fid": "\xA2\x00", "alias": ("geldkarte",)}), + "\xD2\x76\x00\x00\x25\x47\x41\x01\x00": ("DF_GA_MAESTRO", "GA-Maestro", {"fid": "\xAC\x00"}), + "\xD2\x76\x00\x00\x25\x54\x44\x01\x00": ("DF_TAN", "TAN-Anwendung", {"fid": "\xAC\x02"}), + "\xD2\x76\x00\x00\x25\x4D\x01\x02\x00": ("DF_MARKTPLATZ_NEU", "Marktplatz", {"fid": "\xB0\x01"}), + "\xD2\x76\x00\x00\x25\x46\x53\x02\x00": ("DF_FAHRSCHEIN_NEU", "Fahrschein", {"fid": "\xB0\x00"}), + "\xD2\x76\x00\x00\x25\x48\x42\x02\x00": ("DF_BANKING_20" , "HBCI", {"fid": "\xA6\x00"}), + "\xD2\x76\x00\x00\x25\x4E\x50\x01\x00": ("DF_NOTEPAD", "Notepad", {"fid": "\xA6\x10"}), + + "\xd2\x76\x00\x00\x85\x01\x00": ("NFC_TYPE_4", "NFC NDEF Application on tag type 4", {"alias": ("nfc",)}, ), + + # From TR-03110_v201_pdf.pdf + "\xE8\x07\x04\x00\x7f\x00\x07\x03\x02": ("DF_eID", "eID application"), + + "\xd2\x76\x00\x00\x25\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00", "alias": ("vrs",)}, ), + "\xd2\x76\x00\x01\x35\x4b\x41\x4e\x4d\x30\x31\x00": ("VRS_TICKET", "VRS Ticket", {"fid": "\xad\x00",}, ), + } + # Alias for DF_BOERSE_NEU + APPLICATIONS["\xA0\x00\x00\x00\x59\x50\x41\x43\x45\x01\x00"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x45\x50\x02\x00"] + # Alias for DF_GA_MAESTRO + APPLICATIONS["\xA0\x00\x00\x00\x04\x30\x60"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x47\x41\x01\x00"] + + ## Format: "RID (binary)": ("vendor name", [optional: {more information}]) + VENDORS = { + "\xD2\x76\x00\x01\x24": ("Free Software Foundation Europe", ), + "\xD2\x76\x00\x00\x25": ("Bankenverlag", ), + "\xD2\x76\x00\x00\x60": ("Wolfgang Rankl", ), + "\xD2\x76\x00\x00\x05": ("Giesecke & Devrient", ), + "\xD2\x76\x00\x00\x40": ("Zentralinstitut fuer die Kassenaerztliche Versorgung in der Bundesrepublik Deutschland", ), # hpc-use-cases-01.pdf + "\xa0\x00\x00\x02\x47": ("ICAO", ), + "\xa0\x00\x00\x03\x06": ("PC/SC Workgroup", ), + } + + TLV_OBJECTS[TLV_utils.context_FCP] = { + 0x84: (Card.decode_df_name, "DF name"), + } + TLV_OBJECTS[TLV_utils.context_FCI] = TLV_OBJECTS[TLV_utils.context_FCP] + + def __init__(self, reader): + Card.__init__(self, reader) + self.last_sw = None + self.sw_changed = False + + def post_merge(self): + ## Called after cards.__init__.Cardmultiplexer._merge_attributes + self.TLV_OBJECTS[TLV_utils.context_FCP][0x84] = (self._decode_df_name, "DF name") + self.TLV_OBJECTS[TLV_utils.context_FCI][0x84] = (self._decode_df_name, "DF name") + + def decode_statusword(self): + if self.last_sw is None: + return "No command executed so far" + else: + retval = None + + matched_sw = self.match_statusword(self.STATUS_WORDS.keys(), self.last_sw) + if matched_sw is not None: + retval = self.STATUS_WORDS.get(matched_sw) + if isinstance(retval, str): + retval = retval % { "SW1": ord(self.last_sw[0]), + "SW2": ord(self.last_sw[1]) } + + elif callable(retval): + retval = retval( ord(self.last_sw[0]), + ord(self.last_sw[1]) ) + + if retval is None: + return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw) + else: + return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw)) + + def _real_send(self, apdu): + result = Card._real_send(self, apdu) + + self.last_sw = result.sw + self.sw_changed = True + + return result + + + + def verify_pin(self, pin_number, pin_value): + apdu = C_APDU(self.APDU_VERIFY_PIN, P2 = pin_number, + data = pin_value) + result = self.send_apdu(apdu) + return self.check_sw(result.sw) + + def cmd_verify(self, pin_number, pin_value): + """Verify a PIN.""" + pin_number = int(pin_number, 0) + pin_value = binascii.a2b_hex("".join(pin_value.split())) + self.verify_pin(pin_number, pin_value) + + COMMANDS = { + "verify": cmd_verify, + } + diff --git a/cards/java_card.py b/cards/java_card.py index 1a3fb89..2353f4a 100644 --- a/cards/java_card.py +++ b/cards/java_card.py @@ -1,13 +1,10 @@ import utils, binascii -from generic_card import * +from iso_card import * from utils import C_APDU -class Java_Card(Card): +class Java_Card(ISO_Card): DRIVER_NAME = ["Generic Java"] APPLICATIONS = { "\xa0\x00\x00\x00\x01\x01": ("muscle", "MUSCLE applet") } - - def __init__(self, card = None): - Card.__init__(self, card = card) diff --git a/cards/passport_application.py b/cards/passport_application.py index 2aae9c1..943038f 100644 --- a/cards/passport_application.py +++ b/cards/passport_application.py @@ -3,8 +3,7 @@ import struct, binascii, os, datetime, sys from hashlib import sha1 from utils import hexdump, C_APDU from tcos_card import SE_Config, TCOS_Security_Environment -from generic_card import Card -from iso_7816_4_card import ISO_7816_4_Card +from iso_7816_4_card import ISO_7816_4_Card, ISO_Card, Card import crypto_utils, tcos_card, TLV_utils, generic_card from TLV_utils import identifier diff --git a/cards/pn532_card.py b/cards/pn532_card.py index bc84002..4efd8b6 100644 --- a/cards/pn532_card.py +++ b/cards/pn532_card.py @@ -1,13 +1,13 @@ import utils, binascii -from generic_card import * +from iso_card import * -class PN532_Virtual_Card(Card): +class PN532_Virtual_Card(ISO_Card): # This is a virtual card that is enabled for the ACS ACR reader that # contains a PN532 module DRIVER_NAME = ["PN532"] - STATUS_WORDS = dict(Card.STATUS_WORDS) - COMMANDS = dict(Card.COMMANDS) + STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) + COMMANDS = dict(ISO_Card.COMMANDS) APDU_TRANSCEIVE_PN532 = C_APDU(cla=0xff, ins=0, p1=0, p2=0) diff --git a/cards/postcard_card.py b/cards/postcard_card.py index 7a1e917..8fed6eb 100644 --- a/cards/postcard_card.py +++ b/cards/postcard_card.py @@ -4,7 +4,8 @@ from iso_7816_4_card import * class Postcard_Card(ISO_7816_4_Card): DRIVER_NAME = ["Postcard"] CLA = 0xbc - APDU_GET_RESPONSE = C_APDU(cla=CLA,ins=0xc0) + COMMAND_GET_RESPONSE = C_APDU(cla=CLA,ins=0xc0) + APDU_SELECT_FILE = C_APDU(cla=CLA,ins=0xa4) APDU_READ_BINARY = C_APDU(cla=CLA,ins=0xb0,le=0x80) diff --git a/cards/rfid_card.py b/cards/rfid_card.py index 1793cfd..7e29dd7 100644 --- a/cards/rfid_card.py +++ b/cards/rfid_card.py @@ -1,8 +1,8 @@ import utils -from generic_card import * +from iso_card import * import building_blocks -class RFID_Card(Card): +class RFID_Card(ISO_Card): DRIVER_NAME = ["RFID"] APDU_GET_UID = utils.C_APDU(CLA=0xff, INS=0xCA, p1=0, p2=0, Le=0) @@ -32,7 +32,7 @@ class RFID_Card(Card): "get_uid": cmd_get_uid, } - STATUS_WORDS = dict(Card.STATUS_WORDS) + STATUS_WORDS = dict(ISO_Card.STATUS_WORDS) STATUS_WORDS.update( { "\x62\x82": "End of file (or UID) reached before Le bytes", "\x67\x00": "Wrong Length", diff --git a/cards/starcos_card.py b/cards/starcos_card.py index aacf65d..a8ad477 100644 --- a/cards/starcos_card.py +++ b/cards/starcos_card.py @@ -12,7 +12,7 @@ class Starcos_Card(ISO_7816_4_Card): else: return self.select_file(0x00, 0x0C, fid) - ATRS = list(Card.ATRS) + ATRS = list(ISO_Card.ATRS) ATRS.extend( [ ("3bb794008131fe6553504b32339000d1", None), ] ) @@ -129,21 +129,17 @@ def _make_byte_property(prop): lambda self: delattr(self, "_"+prop), "The %s attribute of the APDU" % prop) -class APDU(object): - "Base class for an APDU" - +class Transmission_Frame(object): def __init__(self, *args, **kwargs): - """Creates a new APDU instance. Can be given positional parameters which + """Creates a new frame instance. Can be given positional parameters which must be sequences of either strings (or strings themselves) or integers specifying byte values that will be concatenated in order. Alternatively - you may give exactly one positional argument that is an APDU instance. + you may give exactly one positional argument that is an frame instance. After all the positional arguments have been concatenated they must - form a valid APDU! + form a valid frame! The keyword arguments can then be used to override those values. - Keywords recognized are: - C_APDU: cla, ins, p1, p2, lc, le, data - R_APDU: sw, sw1, sw2, data + Keywords recognized are class-specific, but always include data """ initbuff = list() @@ -168,7 +164,7 @@ class APDU(object): if t == str: initbuff[index] = ord(value) elif t != int: - raise TypeError, "APDU must consist of ints or one-byte strings, not %s (index %s)" % (t, index) + raise TypeError, "Frame must consist of ints or one-byte strings, not %s (index %s)" % (t, index) self.parse( initbuff ) @@ -190,7 +186,7 @@ class APDU(object): del self._data; self.data = "" data = property(_getdata, _setdata, None, - "The data contents of this APDU") + "The data contents of this frame") def _setbyte(self, name, value): #print "setbyte(%r, %r)" % (name, value) @@ -229,8 +225,24 @@ class APDU(object): return "%s(%s)" % (self.__class__.__name__, ", ".join(parts)) -class C_APDU(APDU): - "Class for a command APDU" +class Command_Frame(Transmission_Frame): + pass + +class Response_Frame(Transmission_Frame): + pass + +Transmission_Frame.COMMAND_CLASS = Command_Frame +Transmission_Frame.RESPONSE_CLASS = Response_Frame + +class APDU(Transmission_Frame): + "Base class for an APDU" + +class C_APDU(Command_Frame,APDU): + """Class for a command APDU + + Recognized keywords for __init__ are: + cla, ins, p1, p2, lc, le, data + """ def parse(self, apdu): "Parse a full command APDU and assign the values to our object, overwriting whatever there was." @@ -424,9 +436,12 @@ class C_APDU(APDU): apdu = C_APDU(apdu_head + apdu_tail, marks = marks) return apdu - -class R_APDU(APDU): - "Class for a response APDU" +class R_APDU(Response_Frame,APDU): + """Class for a response APDU + + Recognized keywords for __init__ are: + sw, sw1, sw2, data + """ def _getsw(self): return chr(self.SW1) + chr(self.SW2) def _setsw(self, value): @@ -458,6 +473,9 @@ class R_APDU(APDU): "Return this APDU as a binary string" return self.data + self.sw +APDU.COMMAND_CLASS = C_APDU +APDU.RESPONSE_CLASS = R_APDU + class Raw_APDU(APDU): """Raw APDU that doesn't do any parsing""" @@ -472,7 +490,7 @@ class Raw_APDU(APDU): def _format_fields(self): return "" -class PN532_Frame(APDU): +class PN532_Frame(Transmission_Frame): """This is not really an ISO 7816 APDU, but close enough to use the same class infrastructure.""" @@ -540,12 +558,15 @@ class PN532_Frame(APDU): def render(self): return chr(self.cmd) + chr(self.dir) + self.data -class PN532_Command(PN532_Frame): +class PN532_Command(Command_Frame, PN532_Frame): MATCH_BY_dir = _DEFAULT_DIR = 0xd4 -class PN532_Response(PN532_Frame): +class PN532_Response(Response_Frame, PN532_Frame): MATCH_BY_dir = _DEFAULT_DIR = 0xd5 +PN532_Frame.COMMAND_CLASS = PN532_Command +PN532_Frame.RESPONSE_CLASS = PN532_Response + class PN532_Target(object): TYPE_ISO14443A = "ISO 14443-A" TYPE_ISO14443B = "ISO 14443-B" |