210 lines
8.6 KiB
Python
210 lines
8.6 KiB
Python
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])
|