2005-10-03 07:02:49 +00:00
|
|
|
import crypto_utils, utils, pycsc, binascii, fnmatch
|
2005-09-30 04:13:34 +00:00
|
|
|
from utils import 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:
|
2005-09-30 04:13:34 +00:00
|
|
|
APDU_GET_RESPONSE = APDU("\x00\xC0\x00\x00")
|
|
|
|
APDU_VERIFY_PIN = APDU("\x00\x20\x00\x00")
|
2005-09-29 03:39:14 +00:00
|
|
|
SW_OK = '\x90\x00'
|
|
|
|
ATRS = []
|
|
|
|
DRIVER_NAME = "Generic"
|
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
|
|
|
}
|
2005-09-29 03:39:14 +00:00
|
|
|
|
|
|
|
def __init__(self, card = None):
|
|
|
|
if card is None:
|
|
|
|
self.card = pycsc.pycsc(protocol = pycsc.SCARD_PROTOCOL_ANY)
|
|
|
|
else:
|
|
|
|
self.card = card
|
|
|
|
|
|
|
|
self._i = 0
|
2005-09-29 17:16:27 +00:00
|
|
|
self.last_apdu = None
|
|
|
|
self.last_sw = None
|
|
|
|
self.sw_changed = False
|
2005-09-29 03:39:14 +00:00
|
|
|
|
2005-09-29 21:37:28 +00:00
|
|
|
def verify_pin(self, pin_number, pin_value):
|
2005-09-30 04:13:34 +00:00
|
|
|
apdu = APDU(self.APDU_VERIFY_PIN, P2 = pin_number,
|
2005-10-07 01:50:34 +00:00
|
|
|
content = pin_value)
|
2005-09-29 21:37:28 +00:00
|
|
|
result = self.send_apdu(apdu)
|
|
|
|
return result == self.SW_OK
|
|
|
|
|
|
|
|
def cmd_verify(self, *args):
|
|
|
|
if len(args) != 2:
|
|
|
|
raise TypeError, "Must give exactly two arguments: pin number and pin"
|
|
|
|
pin_number = int(args[0], 0)
|
|
|
|
pin_value = binascii.a2b_hex("".join(args[1].split()))
|
|
|
|
self.verify_pin(pin_number, pin_value)
|
|
|
|
|
2005-09-30 04:39:01 +00:00
|
|
|
def cmd_reset(self, *args):
|
|
|
|
self.card.reconnect(init=pycsc.SCARD_RESET_CARD)
|
|
|
|
|
2005-09-29 21:37:28 +00:00
|
|
|
COMMANDS = {
|
2005-09-30 04:39:01 +00:00
|
|
|
"reset": (cmd_reset, "reset",
|
|
|
|
"""Reset the card."""),
|
2005-09-29 21:37:28 +00:00
|
|
|
"verify": (cmd_verify, "verify pin_number pin_value",
|
|
|
|
"""Verify a PIN.""")
|
|
|
|
}
|
|
|
|
|
2005-09-29 03:39:14 +00:00
|
|
|
def _check_apdu(apdu):
|
|
|
|
if len(apdu) < 4 or ((len(apdu) > 5) and len(apdu) != (ord(apdu[4])+5)):
|
|
|
|
print "Cowardly refusing to send invalid APDU:\n ", utils.hexdump(apdu, indent=2)
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
_check_apdu = staticmethod(_check_apdu)
|
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
def _real_send(self, apdu):
|
|
|
|
if not Card._check_apdu(apdu):
|
|
|
|
raise Exception, "Invalid APDU"
|
|
|
|
if DEBUG:
|
|
|
|
print ">> " + utils.hexdump(apdu, indent = 3)
|
|
|
|
result = self.card.transmit(apdu)
|
|
|
|
self.last_apdu = apdu
|
|
|
|
self.last_sw = result[-2:]
|
|
|
|
self.sw_changed = True
|
|
|
|
if DEBUG:
|
|
|
|
print "<< " + utils.hexdump(result, indent = 3)
|
|
|
|
return result
|
|
|
|
|
2005-09-29 03:39:14 +00:00
|
|
|
def send_apdu(self, apdu):
|
2005-09-30 04:39:01 +00:00
|
|
|
if isinstance(apdu, APDU):
|
|
|
|
apdu = apdu.get_string() ## FIXME
|
2005-09-29 03:39:14 +00:00
|
|
|
if not Card._check_apdu(apdu):
|
|
|
|
raise Exception, "Invalid APDU"
|
|
|
|
if DEBUG:
|
|
|
|
print "%s\nBeginning transaction %i" % ('-'*80, self._i)
|
|
|
|
|
|
|
|
if hasattr(self, "before_send"):
|
|
|
|
apdu = self.before_send(apdu)
|
|
|
|
|
2005-09-29 17:16:27 +00:00
|
|
|
result = self._real_send(apdu)
|
2005-09-29 03:39:14 +00:00
|
|
|
|
|
|
|
if result[0] == '\x61':
|
|
|
|
## Need to call GetResponse
|
2005-09-30 04:13:34 +00:00
|
|
|
gr_apdu = APDU(self.APDU_GET_RESPONSE, le = result[1]).get_string()
|
2005-09-29 17:16:27 +00:00
|
|
|
result = self._real_send(gr_apdu)
|
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
|
|
|
|
2005-09-29 03:39:14 +00:00
|
|
|
return result
|
|
|
|
|
|
|
|
def can_handle(cls, ATR):
|
|
|
|
"""Determine whether this class can handle a card with that ATR."""
|
|
|
|
for (knownatr, mask) in cls.ATRS:
|
|
|
|
if len(knownatr) != len(ATR):
|
|
|
|
continue
|
|
|
|
if crypto_utils.andstring(knownatr, mask) == crypto_utils.andstring(ATR, mask):
|
|
|
|
return True
|
|
|
|
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"
|
2005-10-03 07:02:49 +00:00
|
|
|
else:
|
|
|
|
desc = self.STATUS_WORDS.get(self.last_sw)
|
|
|
|
if desc is not None:
|
|
|
|
return desc
|
|
|
|
else:
|
|
|
|
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):
|
|
|
|
return value % { "SW1": ord(self.last_sw[0]),
|
|
|
|
"SW2": ord(self.last_sw[1]) }
|
|
|
|
elif callable(value):
|
|
|
|
return value( ord(self.last_sw[0]),
|
|
|
|
ord(self.last_sw[1]) )
|
|
|
|
|
|
|
|
return "Unknown SW: %s" % binascii.b2a_hex(self.last_sw)
|