2006-05-22 01:04:32 +00:00
|
|
|
import TLV_utils, crypto_utils, utils, pycsc, binascii, fnmatch, sre
|
2006-05-18 15:45:07 +00:00
|
|
|
from utils import C_APDU, R_APDU
|
2005-09-29 03:39:14 +00:00
|
|
|
|
|
|
|
DEBUG = True
|
2005-09-29 19:05:55 +00:00
|
|
|
#DEBUG = False
|
2005-09-29 03:39:14 +00:00
|
|
|
|
|
|
|
class Card:
|
2006-05-19 00:19:08 +00:00
|
|
|
DRIVER_NAME = "Generic"
|
2006-11-19 04:44:45 +00:00
|
|
|
APDU_GET_RESPONSE = C_APDU(ins=0xc0)
|
|
|
|
APDU_VERIFY_PIN = C_APDU(ins=0x20)
|
2005-09-29 03:39:14 +00:00
|
|
|
SW_OK = '\x90\x00'
|
2006-05-26 04:51:06 +00:00
|
|
|
SW1_RETRY = 0x61 ## If this SW1 is received then GET RESPONSE should be called with SW2
|
2006-05-19 00:19:08 +00:00
|
|
|
## 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
|
2005-09-29 03:39:14 +00:00
|
|
|
ATRS = []
|
2005-10-03 07:02:49 +00:00
|
|
|
## 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
|
|
|
|
## status word, possibly with ? characters marking variable nibbles.
|
|
|
|
## Hexadecimal characters MUST be in uppercase. The values that 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 = {
|
2005-10-03 05:20:14 +00:00
|
|
|
SW_OK: "Normal execution",
|
2005-10-03 07:02:49 +00:00
|
|
|
'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)
|
2005-09-29 17:16:27 +00:00
|
|
|
}
|
2006-06-16 23:14:14 +00:00
|
|
|
## For the format of this dictionary of dictionaries see TLV_utils.tags
|
|
|
|
TLV_OBJECTS = {}
|
2007-01-14 13:46:30 +00:00
|
|
|
|
|
|
|
## Format: "AID (binary)": ("name", [optional: description, {more information}])
|
|
|
|
APPLICATIONS = {
|
2007-01-15 13:34:24 +00:00
|
|
|
"\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", ),
|
2007-01-16 17:14:37 +00:00
|
|
|
"\xD2\x76\x00\x01\x24\x01": ("DF_OpenPGP", "OpenPGP card", {"significant_length": 6} ),
|
2007-01-14 13:46:30 +00:00
|
|
|
## The following are from 0341a.pdf: BSI-DSZ-CC-0341-2006
|
|
|
|
"\xD2\x76\x00\x00\x66\x01": ("DF_SIG", "Signature application", {"fid": "\xAB\x00"}),
|
2007-01-14 14:25:57 +00:00
|
|
|
"\xD2\x76\x00\x00\x25\x5A\x41\x02\x00": ("ZA_MF_NEU", "Zusatzanwendungen", {"fid": "\xA7\x00"}),
|
2007-01-14 13:46:30 +00:00
|
|
|
"\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"}),
|
|
|
|
"\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"}),
|
|
|
|
}
|
2007-01-16 17:14:37 +00:00
|
|
|
# Alias for DF_BOERSE_NEU
|
2007-01-14 13:46:30 +00:00
|
|
|
APPLICATIONS["\xA0\x00\x00\x00\x59\x50\x41\x43\x45\x01\x00"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x45\x50\x02\x00"]
|
2007-01-16 17:14:37 +00:00
|
|
|
# Alias for DF_GA_MAESTRO
|
2007-01-14 13:46:30 +00:00
|
|
|
APPLICATIONS["\xA0\x00\x00\x00\x04\x30\x60"] = APPLICATIONS["\xD2\x76\x00\x00\x25\x47\x41\x01\x00"]
|
2007-01-16 17:14:37 +00:00
|
|
|
|
|
|
|
## Format: "RID (binary)": ("vendor name", [optional: {more information}])
|
|
|
|
VENDORS = {
|
|
|
|
"\xD2\x76\x00\x01\x24": ("OpenPGP", ),
|
|
|
|
"\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 für die Kassenaerztliche Versorgung in der Bundesrepublik Deutschland", ), # hpc-use-cases-01.pdf
|
|
|
|
}
|
2005-09-29 03:39:14 +00:00
|
|
|
|
2007-01-14 14:25:57 +00:00
|
|
|
def _decode_df_name(self, value):
|
|
|
|
result = " " + utils.hexdump(value, short=True)
|
2007-01-16 17:14:37 +00:00
|
|
|
info = None
|
|
|
|
|
2007-01-14 14:25:57 +00:00
|
|
|
if self.APPLICATIONS.has_key(value):
|
|
|
|
info = self.APPLICATIONS[value]
|
2007-01-16 17:14:37 +00:00
|
|
|
else:
|
|
|
|
for aid, i in self.APPLICATIONS.items():
|
|
|
|
if not len(i) > 2 or not i[2].has_key("significant_length"):
|
|
|
|
continue
|
|
|
|
if aid[ :i[2]["significant_length"] ] == value[ :i[2]["significant_length"] ]:
|
|
|
|
info = i
|
|
|
|
break
|
|
|
|
|
|
|
|
result_array = []
|
|
|
|
if info is not None:
|
|
|
|
result_array.append( ("Name", info[0]) )
|
|
|
|
|
|
|
|
if len(info) > 1 and not info[1] is None:
|
|
|
|
result_array.append( ("Description", info[1] ) )
|
|
|
|
|
|
|
|
if self.VENDORS.has_key(value[:5]):
|
|
|
|
result_array.append( ("Vendor", self.VENDORS[ value[:5] ][0]) )
|
|
|
|
|
|
|
|
if len(result_array) > 0:
|
|
|
|
max_len = max( [len(a) for a,b in result_array] + [11] ) + 1
|
|
|
|
result = result + "\n" + "\n".join( [("%%-%is %%s" % max_len) % (a+":",b) for a,b in result_array] )
|
2007-01-14 14:25:57 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
def decode_df_name(value):
|
|
|
|
# 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]
|
|
|
|
|
2006-05-04 23:58:28 +00:00
|
|
|
def __init__(self, card = None, reader = None):
|
2005-09-29 03:39:14 +00:00
|
|
|
if card is None:
|
2006-05-04 23:58:28 +00:00
|
|
|
if reader is None:
|
|
|
|
self.card = pycsc.pycsc(protocol = pycsc.SCARD_PROTOCOL_ANY)
|
|
|
|
else:
|
|
|
|
self.card = pycsc.pycsc(protocol = pycsc.SCARD_PROTOCOL_ANY, reader = reader)
|
2005-09-29 03:39:14 +00:00
|
|
|
else:
|
|
|
|
self.card = card
|
|
|
|
|
|
|
|
self._i = 0
|
2005-09-29 17:16:27 +00:00
|
|
|
self.last_apdu = None
|
|
|
|
self.last_sw = None
|
2006-05-18 20:21:51 +00:00
|
|
|
self.last_result = None
|
2005-09-29 17:16:27 +00:00
|
|
|
self.sw_changed = False
|
2005-09-29 03:39:14 +00:00
|
|
|
|
2007-01-14 14:25:57 +00:00
|
|
|
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")
|
|
|
|
|
2005-09-29 21:37:28 +00:00
|
|
|
def verify_pin(self, pin_number, pin_value):
|
2006-05-18 15:45:07 +00:00
|
|
|
apdu = C_APDU(self.APDU_VERIFY_PIN, P2 = pin_number,
|
|
|
|
data = pin_value)
|
2005-09-29 21:37:28 +00:00
|
|
|
result = self.send_apdu(apdu)
|
2006-05-18 15:45:07 +00:00
|
|
|
return result.sw == self.SW_OK
|
2005-09-29 21:37:28 +00:00
|
|
|
|
2005-10-09 02:06:32 +00:00
|
|
|
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()))
|
2005-09-29 21:37:28 +00:00
|
|
|
self.verify_pin(pin_number, pin_value)
|
|
|
|
|
2005-10-09 02:06:32 +00:00
|
|
|
def cmd_reset(self):
|
|
|
|
"""Reset the card."""
|
2005-09-30 04:39:01 +00:00
|
|
|
self.card.reconnect(init=pycsc.SCARD_RESET_CARD)
|
|
|
|
|
2006-07-21 18:22:40 +00:00
|
|
|
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)
|
|
|
|
if start is not None:
|
|
|
|
start = (lastlen + (int(start,0) % lastlen) ) % lastlen
|
|
|
|
else:
|
|
|
|
start = 0
|
|
|
|
if end is not None:
|
|
|
|
end = (lastlen + (int(end,0) % lastlen) ) % lastlen
|
|
|
|
else:
|
|
|
|
end = lastlen
|
|
|
|
print TLV_utils.decode(self.last_result.data[start:end], tags=self.TLV_OBJECTS)
|
2006-05-22 01:04:32 +00:00
|
|
|
|
2007-01-14 13:46:30 +00:00
|
|
|
_SHOW_APPLICATIONS_FORMAT_STRING = "%(aid)-50s %(name)-20s %(description)-30s"
|
|
|
|
def cmd_show_applications(self):
|
|
|
|
"Show the list of known (by the shell) applications"
|
|
|
|
print self._SHOW_APPLICATIONS_FORMAT_STRING % {"aid": "AID", "name": "Name", "description": "Description"}
|
|
|
|
foo = self.APPLICATIONS.items()
|
|
|
|
foo.sort()
|
|
|
|
for aid, info in foo:
|
|
|
|
print self._SHOW_APPLICATIONS_FORMAT_STRING % {
|
|
|
|
"aid": utils.hexdump(aid, short=True),
|
|
|
|
"name": info[0],
|
|
|
|
"description": len(info) > 1 and info[1] or ""
|
|
|
|
}
|
|
|
|
|
2005-09-29 21:37:28 +00:00
|
|
|
COMMANDS = {
|
2005-10-09 02:06:32 +00:00
|
|
|
"reset": cmd_reset,
|
2006-05-22 01:04:32 +00:00
|
|
|
"verify": cmd_verify,
|
|
|
|
"parse_tlv": cmd_parsetlv,
|
2007-01-14 13:46:30 +00:00
|
|
|
"show_applications": cmd_show_applications,
|
2005-09-29 21:37:28 +00:00
|
|
|
}
|
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
def _real_send(self, apdu):
|
2006-05-18 15:45:07 +00:00
|
|
|
apdu_binary = apdu.render()
|
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
if DEBUG:
|
2006-05-18 15:45:07 +00:00
|
|
|
print ">> " + utils.hexdump(apdu_binary, indent = 3)
|
|
|
|
|
|
|
|
result_binary = self.card.transmit(apdu_binary)
|
|
|
|
result = R_APDU(result_binary)
|
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
self.last_apdu = apdu
|
2006-05-18 15:45:07 +00:00
|
|
|
self.last_sw = result.sw
|
2005-09-29 17:16:27 +00:00
|
|
|
self.sw_changed = True
|
2006-05-18 15:45:07 +00:00
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
if DEBUG:
|
2006-05-18 15:45:07 +00:00
|
|
|
print "<< " + utils.hexdump(result_binary, indent = 3)
|
2005-09-29 17:16:27 +00:00
|
|
|
return result
|
|
|
|
|
2006-05-26 04:51:06 +00:00
|
|
|
def _send_with_retry(self, apdu):
|
|
|
|
result = self._real_send(apdu)
|
|
|
|
|
|
|
|
if result.sw1 == self.SW1_RETRY:
|
|
|
|
## Need to call GetResponse
|
|
|
|
gr_apdu = C_APDU(self.APDU_GET_RESPONSE, le = result.sw2) # FIXME
|
|
|
|
result = R_APDU(self._real_send(gr_apdu))
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2005-09-29 03:39:14 +00:00
|
|
|
def send_apdu(self, apdu):
|
|
|
|
if DEBUG:
|
|
|
|
print "%s\nBeginning transaction %i" % ('-'*80, self._i)
|
|
|
|
|
|
|
|
if hasattr(self, "before_send"):
|
|
|
|
apdu = self.before_send(apdu)
|
|
|
|
|
2006-05-26 04:51:06 +00:00
|
|
|
result = self._send_with_retry(apdu)
|
2005-09-29 03:39:14 +00:00
|
|
|
|
2006-11-21 00:51:32 +00:00
|
|
|
if hasattr(self, "after_send"):
|
|
|
|
result = self.after_send(result)
|
|
|
|
|
2005-09-29 03:39:14 +00:00
|
|
|
if DEBUG:
|
|
|
|
print "Ending transaction %i\n%s\n" % (self._i, '-'*80)
|
|
|
|
self._i = self._i + 1
|
2005-09-29 17:16:27 +00:00
|
|
|
|
2006-05-18 20:21:51 +00:00
|
|
|
self.last_result = result
|
2005-09-29 03:39:14 +00:00
|
|
|
return result
|
|
|
|
|
2005-10-12 02:09:15 +00:00
|
|
|
def can_handle(cls, card):
|
|
|
|
"""Determine whether this class can handle a given pycsc object."""
|
|
|
|
ATR = card.status().get("ATR","")
|
2005-09-29 03:39:14 +00:00
|
|
|
for (knownatr, mask) in cls.ATRS:
|
2006-05-19 00:19:08 +00:00
|
|
|
if mask is None:
|
|
|
|
if sre.match(knownatr, binascii.hexlify(ATR), sre.I):
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
if len(knownatr) != len(ATR):
|
|
|
|
continue
|
|
|
|
if crypto_utils.andstring(knownatr, mask) == crypto_utils.andstring(ATR, mask):
|
|
|
|
return True
|
2005-09-29 03:39:14 +00:00
|
|
|
return False
|
|
|
|
can_handle = classmethod(can_handle)
|
|
|
|
|
|
|
|
def get_prompt(self):
|
|
|
|
return "(%s)" % self.DRIVER_NAME
|
2005-09-29 17:16:27 +00:00
|
|
|
|
|
|
|
def decode_statusword(self):
|
|
|
|
if self.last_sw is None:
|
|
|
|
return "No command executed so far"
|
2006-05-17 18:50:18 +00:00
|
|
|
else:
|
|
|
|
retval = None
|
|
|
|
|
2006-05-19 00:19:08 +00:00
|
|
|
retval = self.STATUS_WORDS.get(self.last_sw)
|
|
|
|
if retval is None:
|
|
|
|
retval = self.STATUS_WORDS.get(binascii.hexlify(self.last_sw).upper())
|
|
|
|
if retval is None:
|
2005-10-03 07:02:49 +00:00
|
|
|
target = binascii.b2a_hex(self.last_sw).upper()
|
|
|
|
for (key, value) in self.STATUS_WORDS.items():
|
|
|
|
if fnmatch.fnmatch(target, key):
|
|
|
|
if isinstance(value, str):
|
2006-05-17 18:50:18 +00:00
|
|
|
retval = value % { "SW1": ord(self.last_sw[0]),
|
2005-10-03 07:02:49 +00:00
|
|
|
"SW2": ord(self.last_sw[1]) }
|
2006-05-17 18:50:18 +00:00
|
|
|
break
|
|
|
|
|
2005-10-03 07:02:49 +00:00
|
|
|
elif callable(value):
|
2006-05-17 18:50:18 +00:00
|
|
|
retval = value( ord(self.last_sw[0]),
|
2005-10-03 07:02:49 +00:00
|
|
|
ord(self.last_sw[1]) )
|
2006-05-17 18:50:18 +00:00
|
|
|
break
|
2005-10-03 07:02:49 +00:00
|
|
|
|
2006-05-17 18:50:18 +00:00
|
|
|
if retval is None:
|
2006-05-18 05:00:19 +00:00
|
|
|
return "Unknown SW (SW %s)" % binascii.b2a_hex(self.last_sw)
|
2006-05-17 18:50:18 +00:00
|
|
|
else:
|
2006-05-18 05:00:19 +00:00
|
|
|
return "%s (SW %s)" % (retval, binascii.b2a_hex(self.last_sw))
|
2006-05-17 18:40:12 +00:00
|
|
|
|
|
|
|
def get_protocol(self):
|
|
|
|
return ((self.card.status()["Protocol"] == pycsc.SCARD_PROTOCOL_T0) and (0,) or (1,))[0]
|
2006-05-22 03:26:13 +00:00
|
|
|
|
|
|
|
def close_card(self):
|
|
|
|
"Disconnect from a card"
|
|
|
|
del self.card # FIXME: anything else to do?
|