Files
vsmartcard/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py
frankmorgner 72f5d00a53 better debug output
git-svn-id: https://vsmartcard.svn.sourceforge.net/svnroot/vsmartcard@695 96b47cad-a561-4643-ad3b-153ac7d7599c
2012-01-25 09:49:50 +00:00

500 lines
18 KiB
Python

#
# Copyright (C) 2011 Dominik Oepen, Frank Morgner
#
# This file is part of virtualsmartcard.
#
# virtualsmartcard is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# virtualsmartcard is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# virtualsmartcard. If not, see <http://www.gnu.org/licenses/>.
from virtualsmartcard.SmartcardSAM import SAM
from virtualsmartcard.SEutils import ControlReferenceTemplate, Security_Environment
from virtualsmartcard.SWutils import SwError, SW
from virtualsmartcard.ConstantDefinitions import CRT_TEMPLATE, SM_Class
from virtualsmartcard.TLVutils import unpack, bertlv_pack
from virtualsmartcard.SmartcardFilesystem import make_property
from virtualsmartcard.utils import inttostring, hexdump
import virtualsmartcard.CryptoUtils as vsCrypto
from chat import CHAT
import pace
class nPA_AT_CRT(ControlReferenceTemplate):
PACE_MRZ = 0x01
PACE_CAN = 0x02
PACE_PIN = 0x03
PACE_PUK = 0x04
def __init__(self):
ControlReferenceTemplate.__init__(self, CRT_TEMPLATE["AT"])
def keyref_is_mrz(self):
if self.keyref == '%c'% self.PACE_MRZ:
return True
return False
def keyref_is_can(self):
if self.keyref == '%c'% self.PACE_CAN:
return True
return False
def keyref_is_pin(self):
if self.keyref == '%c'% self.PACE_PIN:
return True
return False
def keyref_is_puk(self):
if self.keyref == '%c'% self.PACE_PUK:
return True
return False
def parse_SE_config(self, config):
r = 0x9000
try:
ControlReferenceTemplate.parse_SE_config(self, config)
except SwError as e:
structure = unpack(config)
for tlv in structure:
tag, length, value = tlv
if tag == 0x7f4c:
chat = CHAT(bertlv_pack([[tag, length, value]]))
print(chat)
elif tag == 0x67:
self.auxiliary_data = value
elif tag == 0x80 or tag == 0x84 or tag == 0x83:
# handled by ControlReferenceTemplate.parse_SE_config
pass
elif tag == 0x91:
#print "saving eph_pub_key"
self.eph_pub_key = value
else:
raise SwError(SW["ERR_REFNOTUSABLE"])
structure = unpack(config)
pin_ref_str = '%c'% self.PACE_PIN
for tlv in structure:
if [0x83, len(pin_ref_str), pin_ref_str] == tlv:
if self.sam.counter <= 0:
r = 0x63c0
elif self.sam.counter == 1:
r = 0x63c1
elif self.sam.counter == 2:
r = 0x63c2
return r, ""
class nPA_SE(Security_Environment):
# TODO call __eac_abort whenever an error occurred
eac_step = make_property("eac_step", "next step to performed for EAC")
def __init__(self, MF, SAM):
Security_Environment.__init__(self, MF, SAM)
self.at = nPA_AT_CRT()
#This breaks support for 3DES
self.cct.blocklength = 16
self.cct.algorithm = "CC"
self.eac_step = 0
self.sec = None
self.eac_ctx = None
self.ssc = 0
self.ca = "DECVCAeID00102"
def _set_SE(self, p2, data):
sw, resp = Security_Environment._set_SE(self, p2, data)
if self.at.algorithm == "PACE":
self.eac_step = 0
elif self.at.algorithm == "TA":
if self.eac_step != 4:
SwError(SW["ERR_AUTHBLOCKED"])
elif self.at.algorithm == "CA":
if self.eac_step != 5:
SwError(SW["ERR_AUTHBLOCKED"])
return sw, resp
def general_authenticate(self, p1, p2, data):
if (p1, p2) != (0x00, 0x00):
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
if self.eac_step == 0 and self.at.algorithm == "PACE":
return self.__eac_pace_step1(data)
elif self.eac_step == 1 and self.at.algorithm == "PACE":
return self.__eac_pace_step2(data)
elif self.eac_step == 2 and self.at.algorithm == "PACE":
return self.__eac_pace_step3(data)
elif self.eac_step == 3 and self.at.algorithm == "PACE":
return self.__eac_pace_step4(data)
elif self.eac_step == 5 and self.at.algorithm == "CA":
return self.__eac_ca(data)
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
def __eac_abort(self):
pace.EAC_CTX_clear_free(self.eac_ctx)
self.eac_ctx = None
pace.PACE_SEC_clear_free(self.sec)
self.sec = None
@staticmethod
def __unpack_general_authenticate(data):
data_structure = []
structure = unpack(data)
for tlv in structure:
tag, length, value = tlv
if tag == 0x7c:
data_structure = value
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
return data_structure
@staticmethod
def __pack_general_authenticate(data):
tlv_data = bertlv_pack(data)
return bertlv_pack([[0x7c, len(tlv_data), tlv_data]])
def __eac_pace_step1(self, data):
tlv_data = nPA_SE.__unpack_general_authenticate(data)
if tlv_data != []:
raise SwError(SW["WARN_NOINFO63"])
self.__eac_abort()
self.eac_ctx = pace.EAC_CTX_new()
if self.at.keyref_is_mrz():
self.sec = pace.PACE_SEC_new(self.sam.mrz, pace.PACE_MRZ)
elif self.at.keyref_is_can():
self.sec = pace.PACE_SEC_new(self.sam.can, pace.PACE_CAN)
elif self.at.keyref_is_pin():
if self.sam.counter <= 0:
print "Must use PUK to unblock"
raise SwError(SW["WARN_NOINFO63"])
if self.sam.counter == 1 and not self.sam.active:
print "Must use CAN to activate"
return 0x63c1, ""
self.sec = pace.PACE_SEC_new(self.sam.PIN, pace.PACE_PIN)
self.sam.counter -= 1
if self.sam.counter <= 1:
self.sam.active = False
elif self.at.keyref_is_puk():
if self.sam.counter_puk <= 0:
raise SwError(SW["WARN_NOINFO63"])
self.sec = pace.PACE_SEC_new(self.sam.puk, pace.PACE_PUK)
self.sam.counter_puk -= 1
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
ef_card_access = self.mf.select('fid', 0x011c)
ef_card_access_data = ef_card_access.getenc('data')
pace.EAC_CTX_init_ef_cardaccess(ef_card_access_data, self.eac_ctx)
pace.EAC_CTX_init_ca(self.eac_ctx, pace.id_CA_DH_AES_CBC_CMAC_128, 13, None, None)
nonce = pace.buf2string(pace.PACE_STEP1_enc_nonce(self.eac_ctx, self.sec))
resp = nPA_SE.__pack_general_authenticate([[0x80, len(nonce), nonce]])
self.eac_step += 1
return 0x9000, resp
def __eac_pace_step2(self, data):
tlv_data = nPA_SE.__unpack_general_authenticate(data)
pubkey = pace.buf2string(pace.PACE_STEP3A_generate_mapping_data(self.eac_ctx))
for tag, length, value in tlv_data:
if tag == 0x81:
pace.PACE_STEP3A_map_generator(self.eac_ctx, pace.get_buf(value))
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.eac_step += 1
return 0x9000, nPA_SE.__pack_general_authenticate([[0x82, len(pubkey), pubkey]])
def __eac_pace_step3(self, data):
tlv_data = nPA_SE.__unpack_general_authenticate(data)
self.my_pace_eph_pubkey = pace.PACE_STEP3B_generate_ephemeral_key(self.eac_ctx)
if not self.my_pace_eph_pubkey:
pace.print_ossl_err()
raise SwError(SW["WARN_NOINFO63"])
eph_pubkey = pace.buf2string(self.my_pace_eph_pubkey)
for tag, length, value in tlv_data:
if tag == 0x83:
self.pace_opp_pub_key = pace.get_buf(value)
pace.PACE_STEP3B_compute_shared_secret(self.eac_ctx, self.pace_opp_pub_key)
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
self.eac_step += 1
return 0x9000, nPA_SE.__pack_general_authenticate([[0x84, len(eph_pubkey), eph_pubkey]])
def __eac_pace_step4(self, data):
tlv_data = nPA_SE.__unpack_general_authenticate(data)
pace.PACE_STEP3C_derive_keys(self.eac_ctx)
my_token = pace.buf2string(pace.PACE_STEP3D_compute_authentication_token(self.eac_ctx, self.pace_opp_pub_key))
token = ""
for tag, length, value in tlv_data:
if tag == 0x85:
token = value
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
if 1 != pace.PACE_STEP3D_verify_authentication_token(self.eac_ctx, pace.get_buf(token)):
pace.print_ossl_err()
raise SwError(SW["WARN_NOINFO63"])
print "Established PACE channel"
if self.at.keyref_is_can():
if (self.sam.counter == 1):
self.sam.active = True
print "PIN resumed"
elif self.at.keyref_is_pin():
self.sam.active = True
self.sam.counter = 3
elif self.at.keyref_is_puk():
self.sam.active = True
self.sam.counter = 3
print "PIN unblocked"
self.eac_step += 1
self.at.algorithm = "TA"
self.ssc = 0
pace.EAC_CTX_set_encryption_ctx(self.eac_ctx, pace.EAC_ID_PACE)
pace.EAC_CTX_init_ta(self.eac_ctx, None, None, self.ca)
return 0x9000, nPA_SE.__pack_general_authenticate([[0x86, len(my_token), my_token],
[0x87, len(self.ca), self.ca]])
def __eac_ca(self, data):
tlv_data = nPA_SE.__unpack_general_authenticate(data)
pubkey = ""
for tag, length, value in tlv_data:
if tag == 0x80:
pubkey = value
else:
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
pace.CA_STEP4_compute_shared_secret(self.eac_ctx,
pace.get_buf(pubkey))
nonce, token = pace.CA_STEP5_derive_keys(self.eac_ctx,
pace.get_buf(pubkey))
# TODO activate SM
return 0x9000, nPA_SE.__pack_general_authenticate([[0x81,
len(nonce), nonce], [0x82, len(token), token]])
def verify_certificate(self, p1, p2, data):
if (p1, p2) != (0x00, 0xbe):
raise SwError(SW["ERR_INCORRECTPARAMETERS"])
cert = bertlv_pack([[0x7f21, len(data), data]])
if 1 != pace.TA_STEP2_import_certificate(self.eac_ctx, cert):
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
return 0x9000, ""
def external_authenticate(self, p1, p2, data):
"""
Authenticate the terminal to the card. Check whether Terminal correctly
encrypted the given challenge or not
"""
if self.dst.keyref: # TODO check if this is the correct CAR
if self.sam.last_challenge is None:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
id_picc = pace.EAC_Comp(self.eac_ctx, pace.EAC_ID_PACE, self.my_pace_eph_pubkey)
# FIXME auxiliary_data might be from an older run of PACE
if hasattr(self, "auxiliary_data"):
auxiliary_data = pace.get_buf(self.auxiliary_data)
else:
auxiliary_data = None
if 1 != pace.TA_STEP6_verify(self.eac_ctx,
pace.get_buf(self.at.eph_pub_key), id_picc,
auxiliary_data, pace.get_buf(data)):
pace.print_ossl_err()
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
print "Terminal's signature verified"
self.eac_step += 1
return 0x9000, ""
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
def compute_cryptographic_checksum(self, p1, p2, data):
checksum = pace.EAC_authenticate(self.eac_ctx, self.ssc, data)
if not checksum:
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
return 0x9000, checksum
def encipher(self, p1, p2, data):
padded = vsCrypto.append_padding(self.cct.blocklength, data)
cipher = pace.EAC_encrypt(self.eac_ctx, self.ssc, padded)
if not cipher:
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
return 0x9000, cipher
def decipher(self, p1, p2, data):
plain = pace.EAC_decrypt(self.eac_ctx, self.ssc, data)
if not plain:
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
return 0x9000, plain
def protect_response(self, sw, result):
"""
This method protects a response APDU using secure messaging mechanisms
:returns: the protected data and the SW bytes
"""
return_data = ""
if result != "":
# Encrypt the data included in the RAPDU
sw, encrypted = self.encipher(0x82, 0x80, result)
encrypted = "\x01" + encrypted
encrypted_tlv = bertlv_pack([(
SM_Class["CRYPTOGRAM_PADDING_INDICATOR_ODD"],
len(encrypted),
encrypted)])
return_data += encrypted_tlv
sw_str = inttostring(sw)
length = len(sw_str)
tag = SM_Class["PLAIN_PROCESSING_STATUS"]
tlv_sw = bertlv_pack([(tag, length, sw_str)])
return_data += tlv_sw
if self.cct.algorithm == None:
raise SwError(SW["CONDITIONSNOTSATISFIED"])
elif self.cct.algorithm == "CC":
tag = SM_Class["CHECKSUM"]
padded = vsCrypto.append_padding(self.cct.blocklength, return_data)
sw, auth = self.compute_cryptographic_checksum(0x8E, 0x80, padded)
length = len(auth)
return_data += bertlv_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 += bertlv_pack([(tag, length, auth)])
return sw, return_data
class nPA_SAM(SAM):
def __init__(self, pin, can, mrz, puk, mf, default_se = nPA_SE):
SAM.__init__(self, pin, None, mf)
self.active = True
self.current_SE = default_se(self.mf, self)
self.can = can
self.mrz = mrz
self.puk = puk
self.counter_puk = 10
def general_authenticate(self, p1, p2, data):
return self.current_SE.general_authenticate(p1, p2, data)
def reset_retry_counter(self, p1, p2, data):
# check if PACE was successful
if self.current_SE.eac_step < 4:
raise SwError(SW["ERR_SECSTATUS"])
# TODO check CAN and PIN for the correct character set
if p1 == 0x02:
# change secret
if p2 == self.current_SE.at.PACE_CAN:
self.can = data
print "Changed CAN to %r" % self.can
elif p2 == self.current_SE.at.PACE_PIN:
# TODO allow terminals to change the PIN with permission "CAN allowed"
if not self.current_SE.at.keyref_is_pin():
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
self.PIN = data
print "Changed PIN to %r" % self.PIN
else:
raise SwError(SW["ERR_DATANOTFOUND"])
elif p1 == 0x03:
# resume/unblock secret
if p2 == self.current_SE.at.PACE_CAN:
# CAN has no counter
pass
elif p2 == self.current_SE.at.PACE_PIN:
if self.current_SE.at.keyref_is_can():
self.active = True
print "Resumed PIN"
elif self.current_SE.at.keyref_is_pin():
# PACE was successful with PIN, nothing to do resume/unblock
pass
elif self.current_SE.at.keyref_is_puk():
# TODO unblock PIN for signature
print "Unblocked PIN"
self.active = True
self.counter = 3
else:
raise SwError(SW["ERR_CONDITIONNOTSATISFIED"])
else:
raise SwError(SW["ERR_DATANOTFOUND"])
else:
raise SwError(SW["ERR_INCORRECTP1P2"])
return 0x9000, ""
def external_authenticate(self, p1, p2, data):
return self.current_SE.external_authenticate(p1, p2, data)
def get_challenge(self, p1, p2, data):
if self.current_SE.eac_step == 4:
# TA
if (p1 != 0x00 or p2 != 0x00):
raise SwError(SW["ERR_INCORRECTP1P2"])
self.last_challenge = pace.buf2string(pace.TA_STEP4_get_nonce(self.current_SE.eac_ctx))
if not self.last_challenge:
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
else:
SAM.get_challenge(self, p1, p2, data)
return SW["NORMAL"], self.last_challenge
def parse_SM_CAPDU(self, CAPDU, header_authentication):
self.current_SE.ssc += 1
return SAM.parse_SM_CAPDU(self, CAPDU, header_authentication)
def protect_result(self, sw, unprotected_result):
self.current_SE.ssc += 1
return SAM.protect_result(self, sw, unprotected_result)