Moved Security_Environment code to the SEutils file
git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@497 96b47cad-a561-4643-ad3b-153ac7d7599c
This commit is contained in:
@@ -17,11 +17,14 @@
|
|||||||
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import TLVutils
|
import TLVutils
|
||||||
|
from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING, SM_Class
|
||||||
|
from virtualsmartcard.utils import inttostring, stringtoint, C_APDU
|
||||||
|
from virtualsmartcard.SWutils import SwError, SW
|
||||||
|
import virtualsmartcard.CryptoUtils as vsCrypto
|
||||||
|
|
||||||
|
import struct, logging
|
||||||
from time import time
|
from time import time
|
||||||
from random import seed, randint
|
from random import seed, randint
|
||||||
from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, ALGO_MAPPING
|
|
||||||
from virtualsmartcard.utils import inttostring, stringtoint
|
|
||||||
from virtualsmartcard.SWutils import SwError, SW
|
|
||||||
|
|
||||||
class ControlReferenceTemplate:
|
class ControlReferenceTemplate:
|
||||||
"""
|
"""
|
||||||
@@ -154,3 +157,620 @@ class ControlReferenceTemplate:
|
|||||||
return self.__config_string
|
return self.__config_string
|
||||||
|
|
||||||
|
|
||||||
|
class Security_Environment(object):
|
||||||
|
|
||||||
|
def __init__(self, MF, SAM):
|
||||||
|
self.mf = MF
|
||||||
|
self.sam = SAM
|
||||||
|
|
||||||
|
self.SEID = None
|
||||||
|
self.sm_objects = ""
|
||||||
|
|
||||||
|
#Control Reference Tables
|
||||||
|
self.at = ControlReferenceTemplate(CRT_TEMPLATE["AT"])
|
||||||
|
self.kat = ControlReferenceTemplate(CRT_TEMPLATE["KAT"])
|
||||||
|
self.ht = ControlReferenceTemplate(CRT_TEMPLATE["HT"])
|
||||||
|
self.cct = ControlReferenceTemplate(CRT_TEMPLATE["CCT"])
|
||||||
|
self.dst = ControlReferenceTemplate(CRT_TEMPLATE["DST"])
|
||||||
|
self.ct = ControlReferenceTemplate(CRT_TEMPLATE["CT"])
|
||||||
|
|
||||||
|
self.capdu_sm = False
|
||||||
|
self.rapdu_sm = False
|
||||||
|
self.internal_auth = False
|
||||||
|
self.externel_auth = False
|
||||||
|
|
||||||
|
def set_MF(self, mf):
|
||||||
|
self.mf = mf
|
||||||
|
|
||||||
|
def manage_security_environment(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
This method is used to store, restore or erase Security Environments
|
||||||
|
or to manipulate the various parameters of the current SE.
|
||||||
|
P1 specifies the operation to perform, p2 is either the SEID for the
|
||||||
|
referred SE or the tag of a control reference template
|
||||||
|
|
||||||
|
P1:
|
||||||
|
b8 b7 b6 b5 b4 b3 b2 b1 Meaning
|
||||||
|
- - - 1 - - - - Secure messaging in command data field
|
||||||
|
- - 1 - - - - - Secure messaging in response data field
|
||||||
|
- 1 - - - - - - Computation, decipherment, internal
|
||||||
|
authentication and key agreement
|
||||||
|
1 - - - - - - - Verification, encipherment, external
|
||||||
|
authentication and key agreement
|
||||||
|
- - - - 0 0 0 1 SET
|
||||||
|
1 1 1 1 0 0 1 0 STORE
|
||||||
|
1 1 1 1 0 0 1 1 RESTORE
|
||||||
|
1 1 1 1 0 1 0 0 ERASE
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd = p1 & 0x0F
|
||||||
|
se = p1 >> 4
|
||||||
|
if(cmd == 0x01):
|
||||||
|
#Secure messaging in command data field
|
||||||
|
if se & 0x01:
|
||||||
|
self.capdu_sm = True
|
||||||
|
#Secure messaging in response data field
|
||||||
|
if se & 0x02:
|
||||||
|
self.rapdu_sm = True
|
||||||
|
#Computation, decipherment, internal authentication and key agreement
|
||||||
|
if se & 0x04:
|
||||||
|
self.internal_auth = True
|
||||||
|
#Verification, encipherment, external authentication and key agreement
|
||||||
|
if se & 0x08:
|
||||||
|
self.external_auth = True
|
||||||
|
return self.__set_SE(p2, data)
|
||||||
|
elif(cmd== 0x02):
|
||||||
|
return self.sam.store_SE(p2)
|
||||||
|
elif(cmd == 0x03):
|
||||||
|
return self.sam.restore_SE(p2)
|
||||||
|
elif(cmd == 0x04):
|
||||||
|
return self.sam.erase_SE(p2)
|
||||||
|
else:
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
|
||||||
|
|
||||||
|
def __set_SE(self, p2, data):
|
||||||
|
"""
|
||||||
|
Manipulate the current Security Environment. P2 is the tag of a
|
||||||
|
control reference template, data contains control reference objects
|
||||||
|
"""
|
||||||
|
|
||||||
|
valid_p2 = (0xA4, 0xA6, 0xB4, 0xB6, 0xB8)
|
||||||
|
if not p2 in valid_p2:
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
if p2 == 0xA4:
|
||||||
|
return self.at.parse_SE_config(data)
|
||||||
|
elif p2 == 0xA6:
|
||||||
|
return self.kat.parse_SE_config(data)
|
||||||
|
elif p2 == 0xAA:
|
||||||
|
return self.ht.parse_SE_config(data)
|
||||||
|
elif p2 == 0xB4:
|
||||||
|
return self.cct.parse_SE_config(data)
|
||||||
|
elif p2 == 0xB6:
|
||||||
|
return self.dst.parse_SE_config(data)
|
||||||
|
elif p2 == 0xB8:
|
||||||
|
return self.ct.parse_SE_config(data)
|
||||||
|
|
||||||
|
def parse_SM_CAPDU(self, CAPDU, header_authentication):
|
||||||
|
"""
|
||||||
|
This methods parses a data field including Secure Messaging objects.
|
||||||
|
SM_header indicates whether or not the header of the message shall be
|
||||||
|
authenticated. It returns an unprotected command APDU
|
||||||
|
@param CAPDU: The protected CAPDU to be parsed
|
||||||
|
@param header_authentication: Wether or not the header should be
|
||||||
|
included in authentication mechanisms
|
||||||
|
@return: Unprotected command APDU
|
||||||
|
"""
|
||||||
|
structure = TLVutils.unpack(CAPDU.data)
|
||||||
|
return_data = ["",]
|
||||||
|
expected = self.sm_objects
|
||||||
|
|
||||||
|
cla = None
|
||||||
|
ins = None
|
||||||
|
p1 = None
|
||||||
|
p2 = None
|
||||||
|
le = None
|
||||||
|
|
||||||
|
if header_authentication:
|
||||||
|
to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\
|
||||||
|
inttostring(CAPDU.p1) + inttostring(CAPDU.p2)
|
||||||
|
to_authenticate = vsCrypto.append_padding("DES-CBC", to_authenticate)
|
||||||
|
else:
|
||||||
|
to_authenticate = ""
|
||||||
|
|
||||||
|
for tlv in structure:
|
||||||
|
tag, length, value = tlv
|
||||||
|
|
||||||
|
if tag % 2 == 1: #Include object in checksum calculation
|
||||||
|
to_authenticate += inttostring(tag) + inttostring(length) + value
|
||||||
|
|
||||||
|
#SM data objects for encapsulating plain values
|
||||||
|
if tag in (SM_Class["PLAIN_VALUE_NO_TLV"],
|
||||||
|
SM_Class["PLAIN_VALUE_NO_TLV_ODD"]):
|
||||||
|
return_data.append(value) #FIXME: Need TLV coding?
|
||||||
|
#Encapsulated SM objects. Parse them
|
||||||
|
#FIXME: Need to pack value into a dummy CAPDU
|
||||||
|
elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"],
|
||||||
|
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]):
|
||||||
|
return_data.append(self.parse_SM_CAPDU(value, header_authentication))
|
||||||
|
#Encapsulated plaintext BER-TLV objects
|
||||||
|
elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"],
|
||||||
|
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]):
|
||||||
|
return_data.append(value)
|
||||||
|
elif tag in (SM_Class["Ne"], SM_Class["Ne_ODD"]):
|
||||||
|
le = value
|
||||||
|
elif tag == SM_Class["PLAIN_COMMAND_HEADER"]:
|
||||||
|
if len(value) != 8:
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
||||||
|
else:
|
||||||
|
cla = value[:2]
|
||||||
|
ins = value[2:4]
|
||||||
|
p1 = value[4:6]
|
||||||
|
p2 = value[6:8]
|
||||||
|
|
||||||
|
#SM data objects for confidentiality
|
||||||
|
if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"],
|
||||||
|
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]):
|
||||||
|
#The Cryptogram includes SM objects.
|
||||||
|
#We decrypt them and parse the objects.
|
||||||
|
plain = self.decipher(tag, 0x80, value)
|
||||||
|
#TODO: Need Le = length
|
||||||
|
return_data.append(self.parse_SM_CAPDU(plain, header_authentication))
|
||||||
|
elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"],
|
||||||
|
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]):
|
||||||
|
#The Cryptogram includes BER-TLV enconded plaintext.
|
||||||
|
#We decrypt them and return the objects.
|
||||||
|
plain = self.decipher(tag, 0x80, value)
|
||||||
|
return_data.append(plain)
|
||||||
|
elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"],
|
||||||
|
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]):
|
||||||
|
#The first byte of the data field indicates the padding to use:
|
||||||
|
"""
|
||||||
|
Value Meaning
|
||||||
|
'00' No further indication
|
||||||
|
'01' Padding as specified in 6.2.3.1
|
||||||
|
'02' No padding
|
||||||
|
'1X' One to four secret keys for enciphering information,
|
||||||
|
not keys ('X' is a bitmap with any value from '0' to 'F')
|
||||||
|
'11' indicates the first key (e.g., an "even" control word
|
||||||
|
in a pay TV system)
|
||||||
|
'12' indicates the second key (e.g., an "odd" control word
|
||||||
|
in a pay TV system)
|
||||||
|
'13' indicates the first key followed by the second key
|
||||||
|
(e.g., a pair of control words in a pay TV system)
|
||||||
|
'2X' Secret key for enciphering keys, not information
|
||||||
|
('X' is a reference with any value from '0' to 'F')
|
||||||
|
(e.g., in a pay TV system, either an operational key
|
||||||
|
for enciphering control words, or a management key for
|
||||||
|
enciphering operational keys)
|
||||||
|
'3X' Private key of an asymmetric key pair ('X' is a
|
||||||
|
reference with any value from '0' to 'F')
|
||||||
|
'4X' Password ('X' is a reference with any value from '0' to
|
||||||
|
'F')
|
||||||
|
'80' to '8E' Proprietary
|
||||||
|
"""
|
||||||
|
padding_indicator = stringtoint(value[0])
|
||||||
|
sw, plain = self.decipher(tag, 0x80, value[1:])
|
||||||
|
plain = vsCrypto.strip_padding(self.ct.algorithm, plain,
|
||||||
|
padding_indicator)
|
||||||
|
return_data.append(plain)
|
||||||
|
|
||||||
|
#SM data objects for authentication
|
||||||
|
if tag == SM_Class["CHECKSUM"]:
|
||||||
|
auth = vsCrypto.append_padding("DES-CBC", to_authenticate)
|
||||||
|
sw, checksum = self.compute_cryptographic_checksum(0x8E,
|
||||||
|
0x80,
|
||||||
|
auth)
|
||||||
|
if checksum != value:
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
||||||
|
elif tag == SM_Class["DIGITAL_SIGNATURE"]:
|
||||||
|
auth = to_authenticate #FIXME: Need padding?
|
||||||
|
sw, signature = self.compute_digital_signature(0x9E, 0x9A, auth)
|
||||||
|
if signature != value:
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
||||||
|
elif tag in (SM_Class["HASH_CODE"], SM_Class["HASH_CODE_ODD"]):
|
||||||
|
sw, hash = self.hash(p1, p2, to_authenticate)
|
||||||
|
if hash != value:
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
||||||
|
|
||||||
|
#Check if we just parsed a expected SM Object:
|
||||||
|
pos = 0
|
||||||
|
while (pos < len (expected)):
|
||||||
|
if expected[pos] == tag:
|
||||||
|
expected = expected[:pos-1] + expected[pos:]
|
||||||
|
break
|
||||||
|
pos += 1
|
||||||
|
|
||||||
|
#Form unprotected CAPDU
|
||||||
|
if cla == None:
|
||||||
|
cla = CAPDU.cla
|
||||||
|
if ins == None:
|
||||||
|
ins = CAPDU.ins
|
||||||
|
if p1 == None:
|
||||||
|
p1 = CAPDU.p1
|
||||||
|
if p2 == None:
|
||||||
|
p2 = CAPDU.p2
|
||||||
|
if le == None:
|
||||||
|
le = CAPDU.le
|
||||||
|
if expected != "":
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
||||||
|
|
||||||
|
c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data))
|
||||||
|
return c
|
||||||
|
|
||||||
|
def protect_response(self, sw, result):
|
||||||
|
"""
|
||||||
|
This method protects a response APDU using secure messaging mechanisms
|
||||||
|
It returns the protected data and the SW bytes
|
||||||
|
"""
|
||||||
|
expected = self.sm_objects
|
||||||
|
for pos in range(len(expected)):
|
||||||
|
tag = expected[pos]
|
||||||
|
|
||||||
|
return_data = ""
|
||||||
|
#if sw == SW["NORMAL"]:
|
||||||
|
# sw = inttostring(sw)
|
||||||
|
# length = len(sw)
|
||||||
|
# tag = SM_Class["PLAIN_PROCESSING_STATUS"]
|
||||||
|
# tlv_sw = TLVutils.pack([(tag,length,sw)])
|
||||||
|
# return_data += tlv_sw
|
||||||
|
|
||||||
|
if result != "": # Encrypt the data included in the RAPDU
|
||||||
|
sw, encrypted = self.encipher(0x82, 0x80, result)
|
||||||
|
encrypted = "\x01" + encrypted
|
||||||
|
encrypted_tlv = TLVutils.pack([(
|
||||||
|
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
|
||||||
|
len(encrypted),
|
||||||
|
encrypted)])
|
||||||
|
return_data += encrypted_tlv
|
||||||
|
|
||||||
|
if sw == SW["NORMAL"]:
|
||||||
|
if self.cct.algorithm == None:
|
||||||
|
raise SwError(SW["CONDITIONSNOTSATISFIED"])
|
||||||
|
elif self.cct.algorithm == "CCT":
|
||||||
|
tag = SM_Class["CHECKSUM"]
|
||||||
|
to_auth = vsCrypto.append_padding("DES-ECB", return_data)
|
||||||
|
sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, to_auth)
|
||||||
|
length = len(auth)
|
||||||
|
return_data += TLVutils.pack([(tag, length, auth)])
|
||||||
|
elif self.cct.algorithm == "SIGNATURE":
|
||||||
|
tag = SM_Class["DIGITAL_SIGNATURE"]
|
||||||
|
hash = self.hash(0x90, 0x80, return_data)
|
||||||
|
sw, auth = self.compute_digital_signature(0x9E, 0x9A, hash)
|
||||||
|
length = len(auth)
|
||||||
|
return_data += TLVutils.pack([(tag, length, auth)])
|
||||||
|
|
||||||
|
return SW["NORMAL"], return_data
|
||||||
|
|
||||||
|
#The following commands implement ISO 7816-8 {{{
|
||||||
|
def perform_security_operation(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
In the end this command is nothing but a big switch for all the other
|
||||||
|
commands in ISO 7816-8. It will invoke the appropriate command and
|
||||||
|
return its result
|
||||||
|
"""
|
||||||
|
|
||||||
|
allowed_P1P2 = ((0x90, 0x80), (0x90, 0xA0), (0x9E, 0x9A), (0x9E, 0xAC),
|
||||||
|
(0x9E, 0xBC), (0x00, 0xA2), (0x00, 0xA8), (0x00, 0x92),
|
||||||
|
(0x00, 0xAE), (0x00, 0xBE), (0x82, 0x80), (0x84, 0x80),
|
||||||
|
(0x86, 0x80), (0x80, 0x82), (0x80, 0x84), (0x80, 0x86))
|
||||||
|
|
||||||
|
if (p1, p2) not in allowed_P1P2:
|
||||||
|
raise SwError(SW["INCORRECTP1P2"])
|
||||||
|
|
||||||
|
if((p2 in (0x80, 0xA0)) and (p1 == 0x90)):
|
||||||
|
sw, response_data = self.hash(p1, p2, data)
|
||||||
|
elif(p2 in (0x9A, 0xAC, 0xBC) and p1 == 0x9E):
|
||||||
|
sw, response_data = self.compute_digital_signature(p1, p2, data)
|
||||||
|
elif(p2 == 0xA2 and p1 == 0x00):
|
||||||
|
sw, response_data = self.verify_cryptographic_checksum(p1, p2, data)
|
||||||
|
elif(p2 == 0xA8 and p1 == 0x00):
|
||||||
|
sw, response_data = self.verify_digital_signature(p1, p2, data)
|
||||||
|
elif(p2 in (0x92, 0xAE, 0xBE) and p1 == 0x00):
|
||||||
|
sw, response_data = self.verify_certificate(p1, p2, data)
|
||||||
|
elif (p2 == 0x80 and p1 in (0x82, 0x84, 0x86)):
|
||||||
|
sw, response_data = self.encipher(p1, p2, data)
|
||||||
|
elif (p2 in (0x82, 0x84, 0x86) and p1 == 0x80):
|
||||||
|
sw, response_data = self.decipher(p1, p2, data)
|
||||||
|
|
||||||
|
if p1 == 0x00:
|
||||||
|
assert response_data == ""
|
||||||
|
|
||||||
|
return sw, response_data
|
||||||
|
|
||||||
|
|
||||||
|
def compute_cryptographic_checksum(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Compute a cryptographic checksum (e.g. MAC) for the given data.
|
||||||
|
Algorithm and key are specified in the current SE
|
||||||
|
"""
|
||||||
|
if p1 != 0x8E or p2 != 0x80:
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
if self.cct.key == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
|
||||||
|
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
|
||||||
|
data, self.cct.iv)
|
||||||
|
return SW["NORMAL"], checksum
|
||||||
|
|
||||||
|
def compute_digital_signature(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Compute a digital signature for the given data.
|
||||||
|
Algorithm and key are specified in the current SE
|
||||||
|
@param p1: Must be 0x9E = Secure Messaging class for digital signatures
|
||||||
|
@param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data
|
||||||
|
is included in the data field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if p1 != 0x9E or not p2 in (0x9A, 0xAC, 0xBC):
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
|
||||||
|
if self.dst.key == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
|
||||||
|
to_sign = ""
|
||||||
|
if p2 == 0x9A: #Data to be signed
|
||||||
|
to_sign = data
|
||||||
|
elif p2 == 0xAC: #Data objects, sign values
|
||||||
|
to_sign = ""
|
||||||
|
structure = TLVutils.unpack(data)
|
||||||
|
for tag, length, value in structure:
|
||||||
|
to_sign += value
|
||||||
|
elif p2 == 0xBC: #Data objects to be signed
|
||||||
|
pass
|
||||||
|
|
||||||
|
signature = self.dst.key.sign(to_sign, "")
|
||||||
|
return SW["NORMAL"], signature
|
||||||
|
|
||||||
|
def hash(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Hash the given data using the algorithm specified by the
|
||||||
|
current Security environment.
|
||||||
|
Return raw data (no TLV coding).
|
||||||
|
"""
|
||||||
|
if p1 != 0x90 or not p2 in (0x80, 0xA0):
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
algo = self.ht.algorithm
|
||||||
|
if algo == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
try:
|
||||||
|
hash = vsCrypto.hash(algo, data)
|
||||||
|
except ValueError:
|
||||||
|
raise SwError(SW["ERR_EXECUTION"])
|
||||||
|
|
||||||
|
return SW["NORMAL"], hash
|
||||||
|
|
||||||
|
def verify_cryptographic_checksum(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Verify the cryptographic checksum contained in the data field.
|
||||||
|
Data field must contain a cryptographic checksum (tag 0x8E) and a plain
|
||||||
|
value (tag 0x80)
|
||||||
|
"""
|
||||||
|
plain = ""
|
||||||
|
cct = ""
|
||||||
|
|
||||||
|
algo = self.cct.algorithm
|
||||||
|
key = self.cct.key
|
||||||
|
iv = self.cct.iv
|
||||||
|
if algo == None or key == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
|
||||||
|
structure = TLVutils.unpack(data)
|
||||||
|
for tag, length, value in structure:
|
||||||
|
if tag == 0x80:
|
||||||
|
plain = value
|
||||||
|
elif tag == 0x8E:
|
||||||
|
cct = value
|
||||||
|
if plain == "" or cct == "":
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
||||||
|
else:
|
||||||
|
my_cct = vsCrypto.crypto_checksum(algo, key, plain, iv)
|
||||||
|
if my_cct == cct:
|
||||||
|
return SW["NORMAL"], ""
|
||||||
|
else:
|
||||||
|
raise SwError["ERR_SECMESSOBJECTSINCORRECT"]
|
||||||
|
|
||||||
|
def verify_digital_signature(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Verify the digital signature contained in the data field. Data must
|
||||||
|
contain a data to sign (tag 0x9A, 0xAC or 0xBC) and a digital signature
|
||||||
|
(0x9E)
|
||||||
|
"""
|
||||||
|
key = self.dst.key
|
||||||
|
to_sign = ""
|
||||||
|
signature = ""
|
||||||
|
|
||||||
|
if key == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
|
||||||
|
structure = TLVutils.unpack(data)
|
||||||
|
for tag, length, value in structure:
|
||||||
|
if tag == 0x9E:
|
||||||
|
signature = value
|
||||||
|
elif tag == 0x9A: #FIXME: Correct treatment of all possible tags
|
||||||
|
to_sign = value
|
||||||
|
elif tag == 0xAC:
|
||||||
|
pass
|
||||||
|
elif tag == 0xBC:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if to_sign == "" or signature == "":
|
||||||
|
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
||||||
|
|
||||||
|
my_signature = key.sign(value)
|
||||||
|
if my_signature == signature:
|
||||||
|
return SW["NORMAL"], ""
|
||||||
|
else:
|
||||||
|
raise SwError(["ERR_SECMESSOBJECTSINCORRECT"])
|
||||||
|
|
||||||
|
def verify_certificate(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Verify a certificate send by the terminal using the internal trust
|
||||||
|
anchors.
|
||||||
|
This method is currently not implemented.
|
||||||
|
"""
|
||||||
|
if p1 != 0x00 or p2 not in (0x92, 0xAE, 0xBE):
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def encipher(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Encipher data using key, algorithm, IV and Padding specified
|
||||||
|
by the current Security environment.
|
||||||
|
Return raw data (no TLV coding).
|
||||||
|
"""
|
||||||
|
algo = self.ct.algorithm
|
||||||
|
key = self.ct.key
|
||||||
|
if key == None or algo == None:
|
||||||
|
return SW["ERR_CONDITIONNOTSATISFIED"], ""
|
||||||
|
else:
|
||||||
|
padded = vsCrypto.append_padding(algo, data)
|
||||||
|
crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv)
|
||||||
|
return SW["NORMAL"], crypted
|
||||||
|
|
||||||
|
def decipher(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Decipher data using key, algorithm, IV and Padding specified
|
||||||
|
by the current Security environment.
|
||||||
|
Return raw data (no TLV coding). Padding is not removed!!!
|
||||||
|
"""
|
||||||
|
algo = self.ct.algorithm
|
||||||
|
key = self.ct.key
|
||||||
|
if key == None or algo == None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
else:
|
||||||
|
plain = vsCrypto.decrypt(algo, key, data, self.ct.iv)
|
||||||
|
return SW["NORMAL"], plain
|
||||||
|
|
||||||
|
def generate_public_key_pair(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Generate a new public-private key pair.
|
||||||
|
"""
|
||||||
|
from Crypto.PublicKey import RSA, DSA
|
||||||
|
from Crypto.Util.randpool import RandomPool
|
||||||
|
rnd = RandomPool()
|
||||||
|
|
||||||
|
cipher = self.ct.algorithm
|
||||||
|
|
||||||
|
c_class = locals().get(cipher, None)
|
||||||
|
if c_class is None:
|
||||||
|
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
||||||
|
|
||||||
|
if p1 & 0x01 == 0x00: #Generate key
|
||||||
|
PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes)
|
||||||
|
self.dst.key = PublicKey
|
||||||
|
else:
|
||||||
|
pass #Read key
|
||||||
|
|
||||||
|
#Encode keys
|
||||||
|
if cipher == "RSA":
|
||||||
|
#Public key
|
||||||
|
n = str(PublicKey.__getstate__()['n'])
|
||||||
|
e = str(PublicKey.__getstate__()['e'])
|
||||||
|
pk = ((0x81, len(n), n), (0x82, len(e), e))
|
||||||
|
result = TLVutils.bertlv_pack(pk)
|
||||||
|
#result = TLVutils.bertlv_pack((0x7F49, len(pk), pk))
|
||||||
|
#Private key
|
||||||
|
d = PublicKey.__getstate__()['d']
|
||||||
|
elif cipher == "DSA":
|
||||||
|
#DSAParams
|
||||||
|
p = str(PublicKey.__getstate__()['p'])
|
||||||
|
q = str(PublicKey.__getstate__()['q'])
|
||||||
|
g = str(PublicKey.__getstate__()['g'])
|
||||||
|
#Public key
|
||||||
|
y = str(PublicKey.__getstate__()['y'])
|
||||||
|
#TODO: Actual encoding
|
||||||
|
#Private key
|
||||||
|
x = str(PublicKey.__getstate__()['x'])
|
||||||
|
#Add more algorithms here
|
||||||
|
|
||||||
|
|
||||||
|
if p1 & 0x02 == 0x02:
|
||||||
|
return SW["NORMAL"], result
|
||||||
|
else:
|
||||||
|
#FIXME: Where to put the keys?
|
||||||
|
return SW["NORMAL"], ""
|
||||||
|
|
||||||
|
#}}}
|
||||||
|
class CryptoflexSE(Security_Environment):
|
||||||
|
def __init__(self, mf):
|
||||||
|
Security_Environment.__init__(self, mf)
|
||||||
|
|
||||||
|
def generate_public_key_pair(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
In the Cryptoflex card this command only supports RSA keys.
|
||||||
|
|
||||||
|
@param data: Contains the public exponent used for key generation
|
||||||
|
@param p1: The keynumber. Can be used later to refer to the generated key
|
||||||
|
@param p2: Used to specify the keylength.
|
||||||
|
The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024
|
||||||
|
"""
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.Util.randpool import RandomPool
|
||||||
|
|
||||||
|
keynumber = p1 #TODO: Check if key exists
|
||||||
|
|
||||||
|
keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024}
|
||||||
|
|
||||||
|
if not keylength_dict.has_key(p2):
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
else:
|
||||||
|
keylength = keylength_dict[p2]
|
||||||
|
|
||||||
|
rnd = RandomPool()
|
||||||
|
PublicKey = RSA.generate(keylength, rnd.get_bytes)
|
||||||
|
self.dst.key = PublicKey
|
||||||
|
|
||||||
|
e_in = struct.unpack("<i", data)
|
||||||
|
if e_in[0] != 65537:
|
||||||
|
logging.warning("Warning: Exponents different from 65537 are ignored!" +\
|
||||||
|
"The Exponent given is %i" % e_in[0])
|
||||||
|
|
||||||
|
#Encode Public key
|
||||||
|
n = PublicKey.__getstate__()['n']
|
||||||
|
n_str = inttostring(n)
|
||||||
|
n_str = n_str[::-1]
|
||||||
|
e = PublicKey.__getstate__()['e']
|
||||||
|
e_str = inttostring(e, 4)
|
||||||
|
e_str = e_str[::-1]
|
||||||
|
pad = 187 * '\x30' #We don't have CRT components, so we need to pad
|
||||||
|
pk_n = TLVutils.bertlv_pack(((0x81, len(n_str), n_str),
|
||||||
|
(0x01, len(pad), pad),
|
||||||
|
(0x82, len(e_str), e_str)))
|
||||||
|
#Private key
|
||||||
|
d = PublicKey.__getstate__()['d']
|
||||||
|
|
||||||
|
#Write result to FID 10 12 EF-PUB-KEY
|
||||||
|
df = self.mf.currentDF()
|
||||||
|
ef_pub_key = df.select("fid", 0x1012)
|
||||||
|
ef_pub_key.writebinary([0], [pk_n])
|
||||||
|
data = ef_pub_key.getenc('data')
|
||||||
|
|
||||||
|
#Write private key to FID 00 12 EF-PRI-KEY (not necessary?)
|
||||||
|
#How to encode the private key?
|
||||||
|
ef_priv_key = df.select("fid", 0x0012)
|
||||||
|
ef_priv_key.writebinary([0], [inttostring(d)])
|
||||||
|
data = ef_priv_key.getenc('data')
|
||||||
|
return PublicKey
|
||||||
|
|
||||||
|
class ePass_SE(Security_Environment):
|
||||||
|
|
||||||
|
def __init__(self, MF, SE, ssc=None):
|
||||||
|
self.ssc = ssc
|
||||||
|
Security_Environment.__init__(self, MF, SE)
|
||||||
|
|
||||||
|
def compute_cryptographic_checksum(self, p1, p2, data):
|
||||||
|
"""
|
||||||
|
Compute a cryptographic checksum (e.g. MAC) for the given data.
|
||||||
|
Algorithm and key are specified in the current (CAPDU) SE. The ePass
|
||||||
|
uses a Send Sequence Counter for MAC calculation
|
||||||
|
"""
|
||||||
|
if p1 != 0x8E or p2 != 0x80:
|
||||||
|
raise SwError(SW["ERR_INCORRECTP1P2"])
|
||||||
|
|
||||||
|
self.ssc += 1
|
||||||
|
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
|
||||||
|
data, self.cct.iv, self.ssc)
|
||||||
|
|
||||||
|
return SW["NORMAL"], checksum
|
||||||
|
|||||||
@@ -21,12 +21,10 @@ import struct, hashlib, logging
|
|||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
from os import urandom
|
from os import urandom
|
||||||
|
|
||||||
import TLVutils
|
|
||||||
import virtualsmartcard.CryptoUtils as vsCrypto
|
import virtualsmartcard.CryptoUtils as vsCrypto
|
||||||
from virtualsmartcard.SWutils import SwError, SW
|
from virtualsmartcard.SWutils import SwError, SW
|
||||||
from virtualsmartcard.utils import inttostring, stringtoint, hexdump, C_APDU
|
from virtualsmartcard.utils import inttostring, stringtoint
|
||||||
from virtualsmartcard.ConstantDefinitions import SM_Class, CRT_TEMPLATE
|
from virtualsmartcard.SEutils import Security_Environment, CryptoflexSE, ePass_SE
|
||||||
from virtualsmartcard.SEutils import ControlReferenceTemplate as CRT
|
|
||||||
|
|
||||||
def get_referenced_cipher(p1):
|
def get_referenced_cipher(p1):
|
||||||
"""
|
"""
|
||||||
@@ -467,624 +465,6 @@ class CryptoflexSAM(SAM):
|
|||||||
data = data[::-1]
|
data = data[::-1]
|
||||||
return sw, data
|
return sw, data
|
||||||
|
|
||||||
class Security_Environment(object):
|
|
||||||
|
|
||||||
def __init__(self, MF, SAM):
|
|
||||||
self.mf = MF
|
|
||||||
self.sam = SAM
|
|
||||||
|
|
||||||
self.SEID = None
|
|
||||||
self.sm_objects = ""
|
|
||||||
|
|
||||||
#Control Reference Tables
|
|
||||||
self.at = CRT(CRT_TEMPLATE["AT"])
|
|
||||||
self.kat = CRT(CRT_TEMPLATE["KAT"])
|
|
||||||
self.ht = CRT(CRT_TEMPLATE["HT"])
|
|
||||||
self.cct = CRT(CRT_TEMPLATE["CCT"])
|
|
||||||
self.dst = CRT(CRT_TEMPLATE["DST"])
|
|
||||||
self.ct = CRT(CRT_TEMPLATE["CT"])
|
|
||||||
|
|
||||||
self.capdu_sm = False
|
|
||||||
self.rapdu_sm = False
|
|
||||||
self.internal_auth = False
|
|
||||||
self.externel_auth = False
|
|
||||||
|
|
||||||
def set_MF(self, mf):
|
|
||||||
self.mf = mf
|
|
||||||
|
|
||||||
def manage_security_environment(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
This method is used to store, restore or erase Security Environments
|
|
||||||
or to manipulate the various parameters of the current SE.
|
|
||||||
P1 specifies the operation to perform, p2 is either the SEID for the
|
|
||||||
referred SE or the tag of a control reference template
|
|
||||||
|
|
||||||
P1:
|
|
||||||
b8 b7 b6 b5 b4 b3 b2 b1 Meaning
|
|
||||||
- - - 1 - - - - Secure messaging in command data field
|
|
||||||
- - 1 - - - - - Secure messaging in response data field
|
|
||||||
- 1 - - - - - - Computation, decipherment, internal
|
|
||||||
authentication and key agreement
|
|
||||||
1 - - - - - - - Verification, encipherment, external
|
|
||||||
authentication and key agreement
|
|
||||||
- - - - 0 0 0 1 SET
|
|
||||||
1 1 1 1 0 0 1 0 STORE
|
|
||||||
1 1 1 1 0 0 1 1 RESTORE
|
|
||||||
1 1 1 1 0 1 0 0 ERASE
|
|
||||||
"""
|
|
||||||
|
|
||||||
cmd = p1 & 0x0F
|
|
||||||
se = p1 >> 4
|
|
||||||
if(cmd == 0x01):
|
|
||||||
#Secure messaging in command data field
|
|
||||||
if se & 0x01:
|
|
||||||
self.capdu_sm = True
|
|
||||||
#Secure messaging in response data field
|
|
||||||
if se & 0x02:
|
|
||||||
self.rapdu_sm = True
|
|
||||||
#Computation, decipherment, internal authentication and key agreement
|
|
||||||
if se & 0x04:
|
|
||||||
self.internal_auth = True
|
|
||||||
#Verification, encipherment, external authentication and key agreement
|
|
||||||
if se & 0x08:
|
|
||||||
self.external_auth = True
|
|
||||||
return self.__set_SE(p2, data)
|
|
||||||
elif(cmd== 0x02):
|
|
||||||
return self.sam.store_SE(p2)
|
|
||||||
elif(cmd == 0x03):
|
|
||||||
return self.sam.restore_SE(p2)
|
|
||||||
elif(cmd == 0x04):
|
|
||||||
return self.sam.erase_SE(p2)
|
|
||||||
else:
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
|
|
||||||
|
|
||||||
def __set_SE(self, p2, data):
|
|
||||||
"""
|
|
||||||
Manipulate the current Security Environment. P2 is the tag of a
|
|
||||||
control reference template, data contains control reference objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
valid_p2 = (0xA4, 0xA6, 0xB4, 0xB6, 0xB8)
|
|
||||||
if not p2 in valid_p2:
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
if p2 == 0xA4:
|
|
||||||
return self.at.parse_SE_config(data)
|
|
||||||
elif p2 == 0xA6:
|
|
||||||
return self.kat.parse_SE_config(data)
|
|
||||||
elif p2 == 0xAA:
|
|
||||||
return self.ht.parse_SE_config(data)
|
|
||||||
elif p2 == 0xB4:
|
|
||||||
return self.cct.parse_SE_config(data)
|
|
||||||
elif p2 == 0xB6:
|
|
||||||
return self.dst.parse_SE_config(data)
|
|
||||||
elif p2 == 0xB8:
|
|
||||||
return self.ct.parse_SE_config(data)
|
|
||||||
|
|
||||||
def parse_SM_CAPDU(self, CAPDU, header_authentication):
|
|
||||||
"""
|
|
||||||
This methods parses a data field including Secure Messaging objects.
|
|
||||||
SM_header indicates whether or not the header of the message shall be
|
|
||||||
authenticated. It returns an unprotected command APDU
|
|
||||||
@param CAPDU: The protected CAPDU to be parsed
|
|
||||||
@param header_authentication: Wether or not the header should be
|
|
||||||
included in authentication mechanisms
|
|
||||||
@return: Unprotected command APDU
|
|
||||||
"""
|
|
||||||
structure = TLVutils.unpack(CAPDU.data)
|
|
||||||
return_data = ["",]
|
|
||||||
expected = self.sm_objects
|
|
||||||
|
|
||||||
cla = None
|
|
||||||
ins = None
|
|
||||||
p1 = None
|
|
||||||
p2 = None
|
|
||||||
le = None
|
|
||||||
|
|
||||||
if header_authentication:
|
|
||||||
to_authenticate = inttostring(CAPDU.cla) + inttostring(CAPDU.ins)+\
|
|
||||||
inttostring(CAPDU.p1) + inttostring(CAPDU.p2)
|
|
||||||
to_authenticate = vsCrypto.append_padding("DES-CBC", to_authenticate)
|
|
||||||
else:
|
|
||||||
to_authenticate = ""
|
|
||||||
|
|
||||||
for tlv in structure:
|
|
||||||
tag, length, value = tlv
|
|
||||||
|
|
||||||
if tag % 2 == 1: #Include object in checksum calculation
|
|
||||||
to_authenticate += inttostring(tag) + inttostring(length) + value
|
|
||||||
|
|
||||||
#SM data objects for encapsulating plain values
|
|
||||||
if tag in (SM_Class["PLAIN_VALUE_NO_TLV"],
|
|
||||||
SM_Class["PLAIN_VALUE_NO_TLV_ODD"]):
|
|
||||||
return_data.append(value) #FIXME: Need TLV coding?
|
|
||||||
#Encapsulated SM objects. Parse them
|
|
||||||
#FIXME: Need to pack value into a dummy CAPDU
|
|
||||||
elif tag in (SM_Class["PLAIN_VALUE_TLV_INCULDING_SM"],
|
|
||||||
SM_Class["PLAIN_VALUE_TLV_INCULDING_SM_ODD"]):
|
|
||||||
return_data.append(self.parse_SM_CAPDU(value, header_authentication))
|
|
||||||
#Encapsulated plaintext BER-TLV objects
|
|
||||||
elif tag in (SM_Class["PLAIN_VALUE_TLV_NO_SM"],
|
|
||||||
SM_Class["PLAIN_VALUE_TLV_NO_SM_ODD"]):
|
|
||||||
return_data.append(value)
|
|
||||||
elif tag in (SM_Class["Ne"], SM_Class["Ne_ODD"]):
|
|
||||||
le = value
|
|
||||||
elif tag == SM_Class["PLAIN_COMMAND_HEADER"]:
|
|
||||||
if len(value) != 8:
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
|
||||||
else:
|
|
||||||
cla = value[:2]
|
|
||||||
ins = value[2:4]
|
|
||||||
p1 = value[4:6]
|
|
||||||
p2 = value[6:8]
|
|
||||||
|
|
||||||
#SM data objects for confidentiality
|
|
||||||
if tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM"],
|
|
||||||
SM_Class["CRYPTOGRAM_PLAIN_TLV_INCLUDING_SM_ODD"]):
|
|
||||||
#The Cryptogram includes SM objects.
|
|
||||||
#We decrypt them and parse the objects.
|
|
||||||
plain = self.decipher(tag, 0x80, value)
|
|
||||||
#TODO: Need Le = length
|
|
||||||
return_data.append(self.parse_SM_CAPDU(plain, header_authentication))
|
|
||||||
elif tag in (SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM"],
|
|
||||||
SM_Class["CRYPTOGRAM_PLAIN_TLV_NO_SM_ODD"]):
|
|
||||||
#The Cryptogram includes BER-TLV enconded plaintext.
|
|
||||||
#We decrypt them and return the objects.
|
|
||||||
plain = self.decipher(tag, 0x80, value)
|
|
||||||
return_data.append(plain)
|
|
||||||
elif tag in (SM_Class["CRYPTOGRAM_PADDING_INDICATOR"],
|
|
||||||
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"]):
|
|
||||||
#The first byte of the data field indicates the padding to use:
|
|
||||||
"""
|
|
||||||
Value Meaning
|
|
||||||
'00' No further indication
|
|
||||||
'01' Padding as specified in 6.2.3.1
|
|
||||||
'02' No padding
|
|
||||||
'1X' One to four secret keys for enciphering information,
|
|
||||||
not keys ('X' is a bitmap with any value from '0' to 'F')
|
|
||||||
'11' indicates the first key (e.g., an "even" control word
|
|
||||||
in a pay TV system)
|
|
||||||
'12' indicates the second key (e.g., an "odd" control word
|
|
||||||
in a pay TV system)
|
|
||||||
'13' indicates the first key followed by the second key
|
|
||||||
(e.g., a pair of control words in a pay TV system)
|
|
||||||
'2X' Secret key for enciphering keys, not information
|
|
||||||
('X' is a reference with any value from '0' to 'F')
|
|
||||||
(e.g., in a pay TV system, either an operational key
|
|
||||||
for enciphering control words, or a management key for
|
|
||||||
enciphering operational keys)
|
|
||||||
'3X' Private key of an asymmetric key pair ('X' is a
|
|
||||||
reference with any value from '0' to 'F')
|
|
||||||
'4X' Password ('X' is a reference with any value from '0' to
|
|
||||||
'F')
|
|
||||||
'80' to '8E' Proprietary
|
|
||||||
"""
|
|
||||||
padding_indicator = stringtoint(value[0])
|
|
||||||
sw, plain = self.decipher(tag, 0x80, value[1:])
|
|
||||||
plain = vsCrypto.strip_padding(self.ct.algorithm, plain,
|
|
||||||
padding_indicator)
|
|
||||||
return_data.append(plain)
|
|
||||||
|
|
||||||
#SM data objects for authentication
|
|
||||||
if tag == SM_Class["CHECKSUM"]:
|
|
||||||
auth = vsCrypto.append_padding("DES-CBC", to_authenticate)
|
|
||||||
sw, checksum = self.compute_cryptographic_checksum(0x8E,
|
|
||||||
0x80,
|
|
||||||
auth)
|
|
||||||
if checksum != value:
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
|
||||||
elif tag == SM_Class["DIGITAL_SIGNATURE"]:
|
|
||||||
auth = to_authenticate #FIXME: Need padding?
|
|
||||||
sw, signature = self.compute_digital_signature(0x9E, 0x9A, auth)
|
|
||||||
if signature != value:
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
|
||||||
elif tag in (SM_Class["HASH_CODE"], SM_Class["HASH_CODE_ODD"]):
|
|
||||||
sw, hash = self.hash(p1, p2, to_authenticate)
|
|
||||||
if hash != value:
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSINCORRECT"])
|
|
||||||
|
|
||||||
#Check if we just parsed a expected SM Object:
|
|
||||||
pos = 0
|
|
||||||
while (pos < len (expected)):
|
|
||||||
if expected[pos] == tag:
|
|
||||||
expected = expected[:pos-1] + expected[pos:]
|
|
||||||
break
|
|
||||||
pos += 1
|
|
||||||
|
|
||||||
#Form unprotected CAPDU
|
|
||||||
if cla == None:
|
|
||||||
cla = CAPDU.cla
|
|
||||||
if ins == None:
|
|
||||||
ins = CAPDU.ins
|
|
||||||
if p1 == None:
|
|
||||||
p1 = CAPDU.p1
|
|
||||||
if p2 == None:
|
|
||||||
p2 = CAPDU.p2
|
|
||||||
if le == None:
|
|
||||||
le = CAPDU.le
|
|
||||||
if expected != "":
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
|
||||||
|
|
||||||
c = C_APDU(cla=cla, ins=ins, p1=p1, p2=p2, le=le, data="".join(return_data))
|
|
||||||
return c
|
|
||||||
|
|
||||||
def protect_response(self, sw, result):
|
|
||||||
"""
|
|
||||||
This method protects a response APDU using secure messaging mechanisms
|
|
||||||
It returns the protected data and the SW bytes
|
|
||||||
"""
|
|
||||||
expected = self.sm_objects
|
|
||||||
for pos in range(len(expected)):
|
|
||||||
tag = expected[pos]
|
|
||||||
|
|
||||||
return_data = ""
|
|
||||||
#if sw == SW["NORMAL"]:
|
|
||||||
# sw = inttostring(sw)
|
|
||||||
# length = len(sw)
|
|
||||||
# tag = SM_Class["PLAIN_PROCESSING_STATUS"]
|
|
||||||
# tlv_sw = TLVutils.pack([(tag,length,sw)])
|
|
||||||
# return_data += tlv_sw
|
|
||||||
|
|
||||||
if result != "": # Encrypt the data included in the RAPDU
|
|
||||||
sw, encrypted = self.encipher(0x82, 0x80, result)
|
|
||||||
encrypted = "\x01" + encrypted
|
|
||||||
encrypted_tlv = TLVutils.pack([(
|
|
||||||
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
|
|
||||||
len(encrypted),
|
|
||||||
encrypted)])
|
|
||||||
return_data += encrypted_tlv
|
|
||||||
|
|
||||||
if sw == SW["NORMAL"]:
|
|
||||||
if self.cct.algorithm == None:
|
|
||||||
raise SwError(SW["CONDITIONSNOTSATISFIED"])
|
|
||||||
elif self.cct.algorithm == "CCT":
|
|
||||||
tag = SM_Class["CHECKSUM"]
|
|
||||||
to_auth = vsCrypto.append_padding("DES-ECB", return_data)
|
|
||||||
sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, to_auth)
|
|
||||||
length = len(auth)
|
|
||||||
return_data += TLVutils.pack([(tag, length, auth)])
|
|
||||||
elif self.cct.algorithm == "SIGNATURE":
|
|
||||||
tag = SM_Class["DIGITAL_SIGNATURE"]
|
|
||||||
hash = self.hash(0x90, 0x80, return_data)
|
|
||||||
sw, auth = self.compute_digital_signature(0x9E, 0x9A, hash)
|
|
||||||
length = len(auth)
|
|
||||||
return_data += TLVutils.pack([(tag, length, auth)])
|
|
||||||
|
|
||||||
return SW["NORMAL"], return_data
|
|
||||||
|
|
||||||
#The following commands implement ISO 7816-8 {{{
|
|
||||||
def perform_security_operation(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
In the end this command is nothing but a big switch for all the other
|
|
||||||
commands in ISO 7816-8. It will invoke the appropriate command and
|
|
||||||
return its result
|
|
||||||
"""
|
|
||||||
|
|
||||||
allowed_P1P2 = ((0x90, 0x80), (0x90, 0xA0), (0x9E, 0x9A), (0x9E, 0xAC),
|
|
||||||
(0x9E, 0xBC), (0x00, 0xA2), (0x00, 0xA8), (0x00, 0x92),
|
|
||||||
(0x00, 0xAE), (0x00, 0xBE), (0x82, 0x80), (0x84, 0x80),
|
|
||||||
(0x86, 0x80), (0x80, 0x82), (0x80, 0x84), (0x80, 0x86))
|
|
||||||
|
|
||||||
if (p1, p2) not in allowed_P1P2:
|
|
||||||
raise SwError(SW["INCORRECTP1P2"])
|
|
||||||
|
|
||||||
if((p2 in (0x80, 0xA0)) and (p1 == 0x90)):
|
|
||||||
sw, response_data = self.hash(p1, p2, data)
|
|
||||||
elif(p2 in (0x9A, 0xAC, 0xBC) and p1 == 0x9E):
|
|
||||||
sw, response_data = self.compute_digital_signature(p1, p2, data)
|
|
||||||
elif(p2 == 0xA2 and p1 == 0x00):
|
|
||||||
sw, response_data = self.verify_cryptographic_checksum(p1, p2, data)
|
|
||||||
elif(p2 == 0xA8 and p1 == 0x00):
|
|
||||||
sw, response_data = self.verify_digital_signature(p1, p2, data)
|
|
||||||
elif(p2 in (0x92, 0xAE, 0xBE) and p1 == 0x00):
|
|
||||||
sw, response_data = self.verify_certificate(p1, p2, data)
|
|
||||||
elif (p2 == 0x80 and p1 in (0x82, 0x84, 0x86)):
|
|
||||||
sw, response_data = self.encipher(p1, p2, data)
|
|
||||||
elif (p2 in (0x82, 0x84, 0x86) and p1 == 0x80):
|
|
||||||
sw, response_data = self.decipher(p1, p2, data)
|
|
||||||
|
|
||||||
if p1 == 0x00:
|
|
||||||
assert response_data == ""
|
|
||||||
|
|
||||||
return sw, response_data
|
|
||||||
|
|
||||||
|
|
||||||
def compute_cryptographic_checksum(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Compute a cryptographic checksum (e.g. MAC) for the given data.
|
|
||||||
Algorithm and key are specified in the current SE
|
|
||||||
"""
|
|
||||||
if p1 != 0x8E or p2 != 0x80:
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
if self.cct.key == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
|
|
||||||
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
|
|
||||||
data, self.cct.iv)
|
|
||||||
return SW["NORMAL"], checksum
|
|
||||||
|
|
||||||
def compute_digital_signature(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Compute a digital signature for the given data.
|
|
||||||
Algorithm and key are specified in the current SE
|
|
||||||
@param p1: Must be 0x9E = Secure Messaging class for digital signatures
|
|
||||||
@param p2: Must be one of 0x9A, 0xAC, 0xBC. Indicates what kind of data
|
|
||||||
is included in the data field.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if p1 != 0x9E or not p2 in (0x9A, 0xAC, 0xBC):
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
|
|
||||||
if self.dst.key == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
|
|
||||||
to_sign = ""
|
|
||||||
if p2 == 0x9A: #Data to be signed
|
|
||||||
to_sign = data
|
|
||||||
elif p2 == 0xAC: #Data objects, sign values
|
|
||||||
to_sign = ""
|
|
||||||
structure = TLVutils.unpack(data)
|
|
||||||
for tag, length, value in structure:
|
|
||||||
to_sign += value
|
|
||||||
elif p2 == 0xBC: #Data objects to be signed
|
|
||||||
pass
|
|
||||||
|
|
||||||
signature = self.dst.key.sign(to_sign, "")
|
|
||||||
return SW["NORMAL"], signature
|
|
||||||
|
|
||||||
def hash(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Hash the given data using the algorithm specified by the
|
|
||||||
current Security environment.
|
|
||||||
Return raw data (no TLV coding).
|
|
||||||
"""
|
|
||||||
if p1 != 0x90 or not p2 in (0x80, 0xA0):
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
algo = self.ht.algorithm
|
|
||||||
if algo == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
try:
|
|
||||||
hash = vsCrypto.hash(algo, data)
|
|
||||||
except ValueError:
|
|
||||||
raise SwError(SW["ERR_EXECUTION"])
|
|
||||||
|
|
||||||
return SW["NORMAL"], hash
|
|
||||||
|
|
||||||
def verify_cryptographic_checksum(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Verify the cryptographic checksum contained in the data field.
|
|
||||||
Data field must contain a cryptographic checksum (tag 0x8E) and a plain
|
|
||||||
value (tag 0x80)
|
|
||||||
"""
|
|
||||||
plain = ""
|
|
||||||
cct = ""
|
|
||||||
|
|
||||||
algo = self.cct.algorithm
|
|
||||||
key = self.cct.key
|
|
||||||
iv = self.cct.iv
|
|
||||||
if algo == None or key == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
|
|
||||||
structure = TLVutils.unpack(data)
|
|
||||||
for tag, length, value in structure:
|
|
||||||
if tag == 0x80:
|
|
||||||
plain = value
|
|
||||||
elif tag == 0x8E:
|
|
||||||
cct = value
|
|
||||||
if plain == "" or cct == "":
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
|
||||||
else:
|
|
||||||
my_cct = vsCrypto.crypto_checksum(algo, key, plain, iv)
|
|
||||||
if my_cct == cct:
|
|
||||||
return SW["NORMAL"], ""
|
|
||||||
else:
|
|
||||||
raise SwError["ERR_SECMESSOBJECTSINCORRECT"]
|
|
||||||
|
|
||||||
def verify_digital_signature(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Verify the digital signature contained in the data field. Data must
|
|
||||||
contain a data to sign (tag 0x9A, 0xAC or 0xBC) and a digital signature
|
|
||||||
(0x9E)
|
|
||||||
"""
|
|
||||||
key = self.dst.key
|
|
||||||
to_sign = ""
|
|
||||||
signature = ""
|
|
||||||
|
|
||||||
if key == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
|
|
||||||
structure = TLVutils.unpack(data)
|
|
||||||
for tag, length, value in structure:
|
|
||||||
if tag == 0x9E:
|
|
||||||
signature = value
|
|
||||||
elif tag == 0x9A: #FIXME: Correct treatment of all possible tags
|
|
||||||
to_sign = value
|
|
||||||
elif tag == 0xAC:
|
|
||||||
pass
|
|
||||||
elif tag == 0xBC:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if to_sign == "" or signature == "":
|
|
||||||
raise SwError(SW["ERR_SECMESSOBJECTSMISSING"])
|
|
||||||
|
|
||||||
my_signature = key.sign(value)
|
|
||||||
if my_signature == signature:
|
|
||||||
return SW["NORMAL"], ""
|
|
||||||
else:
|
|
||||||
raise SwError(["ERR_SECMESSOBJECTSINCORRECT"])
|
|
||||||
|
|
||||||
def verify_certificate(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Verify a certificate send by the terminal using the internal trust
|
|
||||||
anchors.
|
|
||||||
This method is currently not implemented.
|
|
||||||
"""
|
|
||||||
if p1 != 0x00 or p2 not in (0x92, 0xAE, 0xBE):
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
else:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def encipher(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Encipher data using key, algorithm, IV and Padding specified
|
|
||||||
by the current Security environment.
|
|
||||||
Return raw data (no TLV coding).
|
|
||||||
"""
|
|
||||||
algo = self.ct.algorithm
|
|
||||||
key = self.ct.key
|
|
||||||
if key == None or algo == None:
|
|
||||||
return SW["ERR_CONDITIONNOTSATISFIED"], ""
|
|
||||||
else:
|
|
||||||
padded = vsCrypto.append_padding(algo, data)
|
|
||||||
crypted = vsCrypto.encrypt(algo, key, padded, self.ct.iv)
|
|
||||||
return SW["NORMAL"], crypted
|
|
||||||
|
|
||||||
def decipher(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Decipher data using key, algorithm, IV and Padding specified
|
|
||||||
by the current Security environment.
|
|
||||||
Return raw data (no TLV coding). Padding is not removed!!!
|
|
||||||
"""
|
|
||||||
algo = self.ct.algorithm
|
|
||||||
key = self.ct.key
|
|
||||||
if key == None or algo == None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
else:
|
|
||||||
plain = vsCrypto.decrypt(algo, key, data, self.ct.iv)
|
|
||||||
return SW["NORMAL"], plain
|
|
||||||
|
|
||||||
def generate_public_key_pair(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Generate a new public-private key pair.
|
|
||||||
"""
|
|
||||||
from Crypto.PublicKey import RSA, DSA
|
|
||||||
from Crypto.Util.randpool import RandomPool
|
|
||||||
rnd = RandomPool()
|
|
||||||
|
|
||||||
cipher = self.ct.algorithm
|
|
||||||
|
|
||||||
c_class = locals().get(cipher, None)
|
|
||||||
if c_class is None:
|
|
||||||
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
|
|
||||||
|
|
||||||
if p1 & 0x01 == 0x00: #Generate key
|
|
||||||
PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes)
|
|
||||||
self.dst.key = PublicKey
|
|
||||||
else:
|
|
||||||
pass #Read key
|
|
||||||
|
|
||||||
#Encode keys
|
|
||||||
if cipher == "RSA":
|
|
||||||
#Public key
|
|
||||||
n = str(PublicKey.__getstate__()['n'])
|
|
||||||
e = str(PublicKey.__getstate__()['e'])
|
|
||||||
pk = ((0x81, len(n), n), (0x82, len(e), e))
|
|
||||||
result = TLVutils.bertlv_pack(pk)
|
|
||||||
#result = TLVutils.bertlv_pack((0x7F49, len(pk), pk))
|
|
||||||
#Private key
|
|
||||||
d = PublicKey.__getstate__()['d']
|
|
||||||
elif cipher == "DSA":
|
|
||||||
#DSAParams
|
|
||||||
p = str(PublicKey.__getstate__()['p'])
|
|
||||||
q = str(PublicKey.__getstate__()['q'])
|
|
||||||
g = str(PublicKey.__getstate__()['g'])
|
|
||||||
#Public key
|
|
||||||
y = str(PublicKey.__getstate__()['y'])
|
|
||||||
#TODO: Actual encoding
|
|
||||||
#Private key
|
|
||||||
x = str(PublicKey.__getstate__()['x'])
|
|
||||||
#Add more algorithms here
|
|
||||||
|
|
||||||
|
|
||||||
if p1 & 0x02 == 0x02:
|
|
||||||
return SW["NORMAL"], result
|
|
||||||
else:
|
|
||||||
#FIXME: Where to put the keys?
|
|
||||||
return SW["NORMAL"], ""
|
|
||||||
|
|
||||||
#}}}
|
|
||||||
class CryptoflexSE(Security_Environment):
|
|
||||||
def __init__(self, mf):
|
|
||||||
Security_Environment.__init__(self, mf)
|
|
||||||
|
|
||||||
def generate_public_key_pair(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
In the Cryptoflex card this command only supports RSA keys.
|
|
||||||
|
|
||||||
@param data: Contains the public exponent used for key generation
|
|
||||||
@param p1: The keynumber. Can be used later to refer to the generated key
|
|
||||||
@param p2: Used to specify the keylength.
|
|
||||||
The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024
|
|
||||||
"""
|
|
||||||
from Crypto.PublicKey import RSA
|
|
||||||
from Crypto.Util.randpool import RandomPool
|
|
||||||
|
|
||||||
keynumber = p1 #TODO: Check if key exists
|
|
||||||
|
|
||||||
keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024}
|
|
||||||
|
|
||||||
if not keylength_dict.has_key(p2):
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
else:
|
|
||||||
keylength = keylength_dict[p2]
|
|
||||||
|
|
||||||
rnd = RandomPool()
|
|
||||||
PublicKey = RSA.generate(keylength, rnd.get_bytes)
|
|
||||||
self.dst.key = PublicKey
|
|
||||||
|
|
||||||
e_in = struct.unpack("<i", data)
|
|
||||||
if e_in[0] != 65537:
|
|
||||||
logging.warning("Warning: Exponents different from 65537 are ignored!" +\
|
|
||||||
"The Exponent given is %i" % e_in[0])
|
|
||||||
|
|
||||||
#Encode Public key
|
|
||||||
n = PublicKey.__getstate__()['n']
|
|
||||||
n_str = inttostring(n)
|
|
||||||
n_str = n_str[::-1]
|
|
||||||
e = PublicKey.__getstate__()['e']
|
|
||||||
e_str = inttostring(e, 4)
|
|
||||||
e_str = e_str[::-1]
|
|
||||||
pad = 187 * '\x30' #We don't have CRT components, so we need to pad
|
|
||||||
pk_n = TLVutils.bertlv_pack(((0x81, len(n_str), n_str),
|
|
||||||
(0x01, len(pad), pad),
|
|
||||||
(0x82, len(e_str), e_str)))
|
|
||||||
#Private key
|
|
||||||
d = PublicKey.__getstate__()['d']
|
|
||||||
|
|
||||||
#Write result to FID 10 12 EF-PUB-KEY
|
|
||||||
df = self.mf.currentDF()
|
|
||||||
ef_pub_key = df.select("fid", 0x1012)
|
|
||||||
ef_pub_key.writebinary([0], [pk_n])
|
|
||||||
data = ef_pub_key.getenc('data')
|
|
||||||
|
|
||||||
#Write private key to FID 00 12 EF-PRI-KEY (not necessary?)
|
|
||||||
#How to encode the private key?
|
|
||||||
ef_priv_key = df.select("fid", 0x0012)
|
|
||||||
ef_priv_key.writebinary([0], [inttostring(d)])
|
|
||||||
data = ef_priv_key.getenc('data')
|
|
||||||
return PublicKey
|
|
||||||
|
|
||||||
class ePass_SE(Security_Environment):
|
|
||||||
|
|
||||||
def __init__(self, MF, SE, ssc=None):
|
|
||||||
self.ssc = ssc
|
|
||||||
Security_Environment.__init__(self, MF, SE)
|
|
||||||
|
|
||||||
def compute_cryptographic_checksum(self, p1, p2, data):
|
|
||||||
"""
|
|
||||||
Compute a cryptographic checksum (e.g. MAC) for the given data.
|
|
||||||
Algorithm and key are specified in the current (CAPDU) SE. The ePass
|
|
||||||
uses a Send Sequence Counter for MAC calculation
|
|
||||||
"""
|
|
||||||
if p1 != 0x8E or p2 != 0x80:
|
|
||||||
raise SwError(SW["ERR_INCORRECTP1P2"])
|
|
||||||
|
|
||||||
self.ssc += 1
|
|
||||||
checksum = vsCrypto.crypto_checksum(self.cct.algorithm, self.cct.key,
|
|
||||||
data, self.cct.iv, self.ssc)
|
|
||||||
|
|
||||||
return SW["NORMAL"], checksum
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
"""
|
"""
|
||||||
Unit test:
|
Unit test:
|
||||||
|
|||||||
Reference in New Issue
Block a user