cyberflex-shell/cards/cyberflex_card.py

366 lines
16 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 = C_APDU('\x80\x50\x00\x00')
APDU_EXTERNAL_AUTHENTICATE = C_APDU('\x84\x82\x00\x00')
APDU_GET_STATUS = C_APDU('\x84\xF2\x00\x00\x02\x4f\x00')
APDU_DELETE = C_APDU('\x84\xe4\x00\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.cla == 0x84:
## 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:
apdu.lc = apdu.lc + MAC_LENGTH
mac = crypto_utils.calculate_MAC(self.session_key_mac, apdu.render(), self.last_mac)
self.last_mac = mac
apdu.data = apdu.data + 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"
host_challenge = crypto_utils.generate_host_challenge()
apdu = C_APDU(self.APDU_INITIALIZE_UPDATE,
p1 = keyset_version, p2 = key_index,
data = 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 not self.check_sw(result.sw):
raise Exception, "Statusword after InitializeUpdate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
card_challenge = result.data[12:20]
card_cryptogram = result.data[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 = C_APDU(self.APDU_EXTERNAL_AUTHENTICATE,
p1 = security_level, p2 = 0,
data = host_cryptogram)
self.secure_channel_state = SECURE_CHANNEL_MAC
result = self.send_apdu(apdu)
self.secure_channel_state = security_level
if not self.check_sw(result.sw):
self.secure_channel_state = SECURE_CHANNEL_NONE
raise Exception, "Statusword after ExternalAuthenticate was %s. Warning: No successful ExternalAuthenticate; keyset might be locked soon" % binascii.b2a_hex(result[-2:])
return True
def select_application(self, aid):
result = Java_Card.select_application(self, aid)
if self.check(self.last_sw) and aid[:5] != DEFAULT_CARD_MANAGER_AID[:5]:
self.secure_channel_state = SECURE_CHANNEL_NONE
return result
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(
C_APDU(self.APDU_GET_STATUS,
p1 = reference_control)
)
def delete(self, aid):
if aid[:5] == DEFAULT_CARD_MANAGER_AID[:5]:
print "Cowardly refusing to delete the card manager."
raise ValueError, "Undeletable object"
tlvaid = chr(0x4f) + chr(len(aid)) + aid
apdu = C_APDU(self.APDU_DELETE,
data = tlvaid)
result = self.send_apdu(apdu)
return result.data[0] == 0x0
def cmd_delete(self, aid):
"""Delete the object identified by aid."""
aid = binascii.a2b_hex("".join(aid.split()))
self.delete(aid)
def cmd_status(self, reference_control = "0x20"):
"""Execute a GetStatus command and return the result."""
reference_control = int(reference_control, 0)
result = self.get_status(reference_control)
utils.parse_status(result.data)
def cmd_secure(self, keyset_version=None, key_index=None, security_level=None):
"""Open a secure channel.
If given, keyset_version and key_index must be integers while security_level can be one of 0, clear, 1, mac, 3, macenc, mac+enc."""
if keyset_version is None and key_index is None and security_level is None:
arg1 = 0
arg2 = 0
arg3int = SECURE_CHANNEL_MAC
elif keyset_version is not None and key_index is not None and security_level is not None:
arg1 = int(keyset_version,0)
arg2 = int(key_index,0)
if arg1 not in range(256):
raise ValueError, "keyset_version must be between 0 and 255 (inclusive)."
if arg2 not in (0,1):
raise ValueError, "key_index must be 0 or 1."
arg3 = security_level.strip().lower()
try:
arg3int = int(security_level,0)
except:
arg3int = None
if arg3 == "clear":
arg3int = SECURE_CHANNEL_CLEAR
elif arg3 == "mac":
arg3int = SECURE_CHANNEL_MAC
elif arg3 in ("macenc", "mac+enc"):
arg3int = SECURE_CHANNEL_MACENC
else:
raise TypeError, "Must give none or three arguments."
self.open_secure_channel(arg1, arg2, arg3int)
def cmd_setkey(self, key_index, key):
"""Set a key in the current keyset.
key_index should be one of 0, all, 1, enc, auth, 2, mac, 3, kek."""
arg1 = key_index.strip().lower()
try:
arg1int = int(arg1,0)
except:
arg1int = None
if len(key) != 16:
arg2 = binascii.a2b_hex("".join(key.split()))
else:
arg2 = key
if len(arg2) != 16:
raise TypeError, "Need either exactly 16 binary bytes or 16 hexadezimal bytes for the key argument."
if arg1int == 0 or arg1 == "all":
all = True
else:
all = False
if all or arg1int == KEY_AUTH or arg1 in("auth","enc"):
self.keyset[KEY_AUTH] = arg2
if all or arg1int == KEY_MAC or arg1 == "mac":
self.keyset[KEY_MAC] = arg2
if all or arg1int == KEY_KEK or arg1 == "kek":
self.keyset[KEY_KEK] = arg2
def cmd_printkeyset(self):
"""Print the current keyset."""
print "ENC,AUTH:", utils.hexdump(self.keyset[KEY_AUTH], short=True)
print "MAC: ", utils.hexdump(self.keyset[KEY_MAC], short=True)
print "KEK: ", utils.hexdump(self.keyset[KEY_KEK], short=True)
def cmd_resetkeyset(self):
"""Reset the keyset to the default keyset for this card."""
self.keyset = dict(DEFAULT_KEYSET)
def cmd_savekeyset(self, filename):
"""Saves the keyset to the named file."""
fd = file(filename, "w")
fd.write(self.keyset[KEY_AUTH])
fd.write(self.keyset[KEY_MAC])
fd.write(self.keyset[KEY_KEK])
fd.close()
def cmd_loadkeyset(self, filename):
"""Loads the keyset from the named file."""
fd = file(filename, "r")
keys = fd.read(16*3)
if len(keys) != 16*3:
del keys
fd.close()
raise ValueError, "The file must must contain three keys of 16 bytes each."
self.keyset[KEY_AUTH] = keys[:16]
self.keyset[KEY_MAC] = keys[16:16*2]
self.keyset[KEY_KEK] = keys[16*2:16*3]
del keys
fd.close()
_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.get_driver_name(),
Cyberflex_Card._secname[self.secure_channel_state])
APPLICATIONS = dict(Java_Card.APPLICATIONS)
APPLICATIONS.update( {
DEFAULT_CARD_MANAGER_AID: ("card_manager", ),
} )
COMMANDS = dict(Java_Card.COMMANDS)
COMMANDS.update( {
"status": cmd_status,
"open_secure_channel": cmd_secure,
"set_key": cmd_setkey,
"print_keyset": cmd_printkeyset,
"reset_keyset": cmd_resetkeyset,
"save_keyset": cmd_savekeyset,
"load_keyset": cmd_loadkeyset,
"delete": cmd_delete
} )
STATUS_WORDS = dict(Java_Card.STATUS_WORDS)
STATUS_WORDS.update( {
"\x62\x83": "The Card Manager is locked (SelectApplication).",
"\x63\x00": "Authentication of the host cryptogram failed.",
"\x63\x10": "More data is available for return than is specified in the Le value.",
"\x64\x00": "Technical problem that has no specified diagnosis.",
"\x65\x81": "Memory failure.",
"\x67\x00": "The specified length of the input data (Lc) is incorrect.",
"\x69\x81": "No key is specified (GetResponse, called internally).",
"\x69\x82": "Security status not satisfied. For example, MAC verification failed, the authentication key is locked, or the current security domain requires DAP verification and no verification data was included with the command.",
"\x69\x83": "The key is blocked (GetResponse, called internally).",
"\x69\x85": """A requirement for using the command is not satisfied. For example:
+ Command issued outside of a secure channel.
+ Current application does not have the required application privilege
or life cycle state.
+ The required preceding command was not present.
+ The object to delete is referenced by another object on the card.""",
"\x69\x87": "The MAC or other verification data is missing (Install).",
"\x69\x99": "Application selection failed (SelectApplication).",
"\x6A\x80": """Invalid or inconsistent input data, including input data that is inconsistent with a command header parameter, and LV/TLV-format elements in the input data that are not self-consistent. For example:
+ Incorrect number of padding bytes, incorrect key used for
encryption, or the specified key set or key index value is invalid.
+ Referenced AID is not found in the card registry or package, or the
newly specified AID already exists in the registry.
+ Inappropriate application privilege byte value (installing security
domain), or card already has a default selected application
(specifying default selected application).
+ First block of input data for a load file is not preceded by the
correct tag and/or valid length, or the load file refers to a
nonexistent package.""",
"\x6A\x81": "Target is locked (SelectApplication).",
"\x6A\x82": "Registry contains no valid application (or no additional valid application) with the specified AID (SelectApplication).",
"\x6A\x84": "Insufficient EEPROM memory available to add the object to the card.",
"\x6A\x86": "Incorrect or unsupported value is specified for P1, P2, or both.",
"\x6A\x88": "Data referred to in P1, P2, or both is not found.",
"\x6D\x00": "Unsupported value entered for the INS byte.",
"\x6E\x00": "Unsupported value entered for the CLA byte.",
"\x6F\x00": "JVM error that has no specified diagnosis.",
"\x90\x00": "Command succeeded.",
"\x94\x81": "Target has an invalid life cycle state.",
"\x94\x84": "Unsupported algorithm ID in input data (PutKey).",
"\x94\x85": "Invalid key check value in input data (PutKey).",
} )
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])