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:
parent
122f2b9020
commit
85c11acfcd
|
@ -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
|
|
@ -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])
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue