Files
vsmartcard/virtualsmartcard/src/vpicc/virtualsmartcard/cards/nPA.py
Frank Morgner c92d923fc8 nPA: removed --ca-pubkey command line option
The public key is extracted from the EF.CardSecurity instead.
2012-10-10 22:02:59 +02:00

567 lines
20 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, ALGO_MAPPING
from virtualsmartcard.TLVutils import unpack, bertlv_pack
from virtualsmartcard.SmartcardFilesystem import make_property
from virtualsmartcard.utils import inttostring
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"])
self.chat = None
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:
self.chat = CHAT(bertlv_pack([[tag, length, value]]))
print(self.chat)
elif tag == 0x67:
self.auxiliary_data = bertlv_pack([[tag, length, value]])
elif tag == 0x80 or tag == 0x84 or tag == 0x83 or tag == 0x91:
# handled by ControlReferenceTemplate.parse_SE_config
pass
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"
self.ca_key = None
self.disable_checks = False
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)
elif self.eac_step == 6:
# TODO implement RI
return SW["NORMAL"], ""
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.data
pace.EAC_CTX_init_ef_cardaccess(ef_card_access_data, self.eac_ctx)
ef_card_security = self.mf.select('fid', 0x011d)
ef_card_security_data = ef_card_security.data
ca_pubkey = pace.CA_get_pubkey(ef_card_security_data)
pace.EAC_CTX_init_ca(self.eac_ctx, pace.id_CA_ECDH_AES_CBC_CMAC_128, 13, self.ca_key, ca_pubkey)
if not self.ca_key:
# we don't have a good CA key, so we simply generate an ephemeral one
comp_pubkey = pace.TA_STEP3_generate_ephemeral_key(self.eac_ctx)
pubkey = pace.CA_STEP2_get_eph_pubkey(self.eac_ctx)
if not comp_pubkey or not pubkey:
pace.print_ossl_err()
raise SwError(SW["WARN_NOINFO63"])
# save public key in EF.CardSecurity (and invalidate the signature)
# FIXME this only works for the default EF.CardSecurity.
# Better use an ASN.1 parser to do this manipulation
ef_card_security = self.mf.select('fid', 0x011d)
ef_card_security_data = ef_card_security.data
ef_card_security_data = ef_card_security_data[:61+4+239+2+1] + pubkey + ef_card_security_data[61+4+239+2+1+len(pubkey):]
ef_card_security.data = ef_card_security_data
nonce = 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.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, 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 = self.my_pace_eph_pubkey
for tag, length, value in tlv_data:
if tag == 0x83:
self.pace_opp_pub_key = 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.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.verify_authentication_token(self.eac_ctx, 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.new_encryption_ctx = pace.EAC_ID_PACE
result = [[0x86, len(my_token), my_token]]
if self.at.chat:
if not pace.EAC_CTX_init_ta(self.eac_ctx, None, None, self.ca):
pace.print_ossl_err()
raise SwError(SW["WARN_NOINFO63"])
result.append([0x87, len(self.ca), self.ca])
if (self.disable_checks):
pace.TA_disable_checks(self.eac_ctx)
return 0x9000, nPA_SE.__pack_general_authenticate(result)
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"])
if pace.CA_STEP4_compute_shared_secret(self.eac_ctx, pubkey) != 1:
pace.print_ossl_err()
raise SwError(SW["ERR_NOINFO69"])
nonce, token = pace.CA_STEP5_derive_keys(self.eac_ctx, pubkey)
self.eac_step += 1
print "Generated Nonce and Authentication Token for CA"
# TODO activate SM
self.new_encryption_ctx = pace.EAC_ID_CA
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"])
print "Imported Certificate"
return ""
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
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.at, "auxiliary_data"):
auxiliary_data = pace.get_buf(self.at.auxiliary_data)
else:
auxiliary_data = None
if 1 != pace.TA_STEP6_verify(self.eac_ctx,
pace.get_buf(self.at.iv), pace.get_buf(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 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 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 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
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)
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)
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.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 verify(self, p1, p2, data):
if (p1 != 0x80 or p2 != 0x00):
raise SwError(SW["ERR_INCORRECTP1P2"])
if self.current_SE.eac_step == 6:
structure = unpack(data)
for tag, length, value in structure:
if tag == 6 and ALGO_MAPPING[value] == "DateOfExpiry":
# hell yes, this is a valid nPA
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "DateOfBirth":
# hell yes, we are old enough
# TODO actually check it...
return SW["NORMAL"], ""
if tag == 6 and ALGO_MAPPING[value] == "CommunityID":
# well OK, we are living there
# TODO actually check it...
return SW["NORMAL"], ""
raise SwError(SW["WARN_NOINFO63"])
def parse_SM_CAPDU(self, CAPDU, header_authentication):
if hasattr(self.current_SE, "new_encryption_ctx"):
if self.current_SE.new_encryption_ctx == pace.EAC_ID_PACE:
protocol = "PACE"
else:
protocol = "CA"
print "switching to new encryption context established in %s:" % protocol
pace.EAC_CTX_print_private(self.current_SE.eac_ctx, 4)
self.current_SE.ssc = 0
pace.EAC_CTX_set_encryption_ctx(self.current_SE.eac_ctx, self.current_SE.new_encryption_ctx)
delattr(self.current_SE, "new_encryption_ctx")
self.current_SE.ssc += 1
return SAM.parse_SM_CAPDU(self, CAPDU, 1)
def protect_result(self, sw, unprotected_result):
self.current_SE.ssc += 1
return SAM.protect_result(self, sw, unprotected_result)