Card specific classes in a separate package

git-svn-id: svn+ssh://localhost/home/henryk/svn/cyberflex-shell/trunk@11 f711b948-2313-0410-aaa9-d29f33439f0b
This commit is contained in:
henryk 2005-09-29 03:39:14 +00:00
parent 122f2b9020
commit 85c11acfcd
4 changed files with 307 additions and 0 deletions

15
cards/__init__.py Normal file
View File

@ -0,0 +1,15 @@
"""This package contains different card-specific modules."""
from generic_card import Card
from java_card import Java_Card
from cyberflex_card import Cyberflex_Card
from sys import modules
def find_class(ATR):
"""Find a card class that supports the card identified by ATR.
Returns the generic card class when no better match is found."""
for card_class in dir(modules[__name__]):
card_class = getattr(modules[__name__], card_class)
if hasattr(card_class, "can_handle"):
if card_class.can_handle(ATR):
return card_class
return Card

209
cards/cyberflex_card.py Normal file
View File

@ -0,0 +1,209 @@
import utils, crypto_utils, binascii
from java_card import *
KEY_AUTH = 0x01
KEY_MAC = 0x02
KEY_KEK = 0x03
DEFAULT_KEYSET = {
KEY_AUTH: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F",
KEY_MAC: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F",
KEY_KEK: "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4A\x4B\x4C\x4D\x4E\x4F"}
DEFAULT_CARD_MANAGER_AID = "\xA0\x00\x00\x00\x03\x00\x00"
SECURE_CHANNEL_NONE = -1
SECURE_CHANNEL_CLEAR = 0
SECURE_CHANNEL_MAC = 1
SECURE_CHANNEL_MACENC = 3
MAC_LENGTH = 8
class Cyberflex_Card(Java_Card):
APDU_INITIALIZE_UPDATE = '\x80\x50\x00\x00\x08'
APDU_EXTERNAL_AUTHENTICATE = '\x84\x82\x00\x00'
APDU_GET_STATUS = '\x84\xF2\x00\x00\x02\x4f\x00'
DRIVER_NAME = "Cyberflex"
ATRS = [
## Cyberflex Access 32k v2 ???
("3B 75 13 00 00 9C 02 02 01 02",
"FF FF FF FF FF FF FF FF FF FF"),
## Cyberflex Access Developer 32k
("3B 17 13 9C 12 00 00 00 00 00",
"FF FF 00 FF 00 00 00 00 00 00"),
## Cyberflex Access e-gate 32K
("3B 75 94 00 00 62 02 02 00 80",
"FF FF FF 00 00 FF FF FF 00 00"),
## Cyberflex Access 32K v4
("3b 76 00 00 00 00 9c 11 01 00 00",
"FF FF FF FF FF FF FF FF FF FF FF"),
## Cyberflex Access 64K v1 (non-FIPS-compliant, softmask 1.1)
("3b 75 00 00 00 29 05 01 01 01",
"FF FF 00 00 00 FF FF FF 00 00"),
## Cyberflex Access 64K v1 (FIPS-compliant, softmask 2.1)
("3b 75 00 00 00 29 05 01 02 01",
"FF FF 00 00 00 FF FF FF 00 00")
]
## Will convert the ATRS to binary strings
ATRS = [(binascii.a2b_hex("".join(_a.split())),
binascii.a2b_hex("".join(_b.split()))) for (_a,_b) in ATRS]
del _a, _b
def __init__(self, card = None, keyset = None):
Java_Card.__init__(self, card = card)
if keyset is not None:
self.keyset = keyset
else:
self.keyset = dict(DEFAULT_KEYSET)
self.card_manager_aid = DEFAULT_CARD_MANAGER_AID
self.session_key_enc = None
self.session_key_mac = None
self.last_mac = None
self.secure_channel_state = SECURE_CHANNEL_NONE
def before_send(self, apdu):
"""Will be called by send_apdu before sending a command APDU.
Is responsible for authenticating/encrypting commands when needed."""
if apdu[0] == '\x84':
## Need security
if self.secure_channel_state == SECURE_CHANNEL_NONE:
raise Exception, "Need security but channel is not established"
if self.secure_channel_state == SECURE_CHANNEL_CLEAR:
return apdu
elif self.secure_channel_state == SECURE_CHANNEL_MAC:
if len(apdu) < 4:
raise Exception, "Malformed APDU"
elif len(apdu) == 4:
apdu = apdu + chr(MAC_LENGTH)
else:
apdu = apdu[:4] + chr( ord(apdu[4]) + MAC_LENGTH ) + apdu[5:]
mac = crypto_utils.calculate_MAC(self.session_key_mac, apdu, self.last_mac)
self.last_mac = mac
apdu = apdu + mac
elif self.secure_channel_state == SECURE_CHANNEL_MACENC:
raise Exception, "MAC+Enc Not implemented yet"
return apdu
def open_secure_channel(self, keyset_version = 0x0, key_index = 0x0,
security_level = SECURE_CHANNEL_MAC):
"""Opens a secure channel by sending an InitializeUpdate and
ExternalAuthenticate.
keyset_version is either the explicit key set version or 0x0 for
the implicit key set version.
key_index is either 0x0 for implicit or 0x1 for explicit key index.
security_level is one of SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC
or SECURE_CHANNEL_MACENC.
Note that SECURE_CHANNEL_CLEAR is only available for cards that
are not secured.
Returns: True on success, generates an exception otherwise.
Warning: Cyberflex Access 64k v2 cards maintain a failure counter
and will lock their key set if they receive 3 InitializeUpdate
commands that are not followed by a successful
ExternalAuthenticate!
If this function does not return True you should not retry
the call, but must closely inspect the situation."""
if security_level not in (SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC, SECURE_CHANNEL_MACENC):
raise ValueError, "security_level must be one of SECURE_CHANNEL_CLEAR, SECURE_CHANNEL_MAC or SECURE_CHANNEL_MACENC"
apdu = self.APDU_INITIALIZE_UPDATE[:2] + \
chr(keyset_version) + \
chr(key_index)
host_challenge = crypto_utils.generate_host_challenge()
apdu = apdu + chr(len(host_challenge)) + \
host_challenge
self.secure_channel_state = SECURE_CHANNEL_NONE
self.last_mac = '\x00' * 8
self.session_key_enc = None
self.session_key_mac = None
result = self.send_apdu(apdu)
if result[-2:] != self.SW_OK:
raise Exception, "Statusword after InitializeUpdate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
card_challenge = result[12:20]
card_cryptogram = result[20:28]
self.session_key_enc = crypto_utils.get_session_key(
self.keyset[KEY_AUTH], host_challenge, card_challenge)
self.session_key_mac = crypto_utils.get_session_key(
self.keyset[KEY_MAC], host_challenge, card_challenge)
if not crypto_utils.verify_card_cryptogram(self.session_key_enc,
host_challenge, card_challenge, card_cryptogram):
raise Exception, "Validation error, card not authenticated. Warning: No successful ExternalAuthenticate; keyset might be locked soon"
host_cryptogram = crypto_utils.calculate_host_cryptogram(
self.session_key_enc, card_challenge, host_challenge)
apdu = self.APDU_EXTERNAL_AUTHENTICATE[:2] + \
chr(security_level) + '\x00' + chr(len(host_cryptogram)) + \
host_cryptogram
self.secure_channel_state = SECURE_CHANNEL_MAC
result = self.send_apdu(apdu)
self.secure_channel_state = security_level
if result[-2:] != self.SW_OK:
raise Exception, "Statusword after ExternalAuthenticate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
self.secure_channel_state = SECURE_CHANNEL_NONE
return True
def get_status(self, reference_control=0x20):
"""Sends a GetStatus APDU und returns the result.
reference_control is either:
0x20 Load files
0x40 Applications
0x60 Applications and load files
0x80 Card manager
0xA0 Card manager and load files
0xC0 Card manager and applications
0xE0 Card manager, applications and load files.
Returns: the response APDU which can be parsed with
utils.parse_status()"""
return self.send_apdu(self.APDU_GET_STATUS[:2] + chr(reference_control)
+ self.APDU_GET_STATUS[3:])
def cmd_status(self, *args):
if len(args) > 1:
raise TypeError, "Can have at most one argument."
if len(args) == 1:
print args
reference_control = int(args[0], 0)
else:
reference_control = 0x20
result = self.get_status(reference_control)
utils.parse_status(result[:-2])
def cmd_secure(self, *args):
self.open_secure_channel()
_secname = {SECURE_CHANNEL_NONE: "",
SECURE_CHANNEL_CLEAR: " [clear]",
SECURE_CHANNEL_MAC: " [MAC]",
SECURE_CHANNEL_MACENC: " [MAC+enc]"}
def get_prompt(self):
return "(%s)%s" % (self.DRIVER_NAME,
Cyberflex_Card._secname[self.secure_channel_state])
COMMANDS = dict(Java_Card.COMMANDS)
COMMANDS.update( {
"status": (cmd_status, "status [reference_control]",
"""Execute a GetStatus command and return the result."""),
"open_secure_channel": (cmd_secure, "open_secure_channel",
"""Open a secure channel with the default parameters (FIXME).""")
} )
if __name__ == "__main__":
c = Cyberflex_Card()
print utils.hexdump( c.select_application(DEFAULT_CARD_MANAGER_AID) )
c.open_secure_channel(security_level = SECURE_CHANNEL_MAC)
utils.parse_status(c.get_status(224)[:-2])

71
cards/generic_card.py Normal file
View File

@ -0,0 +1,71 @@
import crypto_utils, utils, pycsc
DEBUG = True
class Card:
APDU_GET_RESPONSE = "\x00\xC0\x00\x00"
SW_OK = '\x90\x00'
ATRS = []
DRIVER_NAME = "Generic"
COMMANDS = []
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
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)
def send_apdu(self, apdu):
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)
if not Card._check_apdu(apdu):
raise Exception, "Invalid APDU"
if DEBUG:
print ">> " + utils.hexdump(apdu, indent = 3)
result = self.card.transmit(apdu)
if DEBUG:
print "<< " + utils.hexdump(result, indent = 3)
if result[0] == '\x61':
## Need to call GetResponse
gr_apdu = self.APDU_GET_RESPONSE + result[1]
if not Card._check_apdu(gr_apdu):
raise Exception, "Invalid APDU"
if DEBUG:
print ">> " + utils.hexdump(gr_apdu, indent = 3)
result = self.card.transmit(gr_apdu)
if DEBUG:
print "<< " + utils.hexdump(result, indent = 3)
if DEBUG:
print "Ending transaction %i\n%s\n" % (self._i, '-'*80)
self._i = self._i + 1
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

12
cards/java_card.py Normal file
View File

@ -0,0 +1,12 @@
from generic_card import *
class Java_Card(Card):
APDU_SELECT_APPLICATION = "\x00\xa4\x04\x00"
DRIVER_NAME = "Generic Java"
def __init__(self, card = None):
Card.__init__(self, card = card)
def select_application(self, aid):
result = self.send_apdu(self.APDU_SELECT_APPLICATION + chr(len(aid)) + aid)
return result