160 lines
7.6 KiB
Python
160 lines
7.6 KiB
Python
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 _send_with_retry(self, apdu):
|
|
result = self._real_send(apdu)
|
|
|
|
if self.check_sw(result.sw, self.PURPOSE_GET_RESPONSE):
|
|
## Need to call GetResponse
|
|
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, 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))
|
|
|
|
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,
|
|
}
|
|
|