package de.persosim.simulator.protocols.ta;
import static org.globaltester.logging.BasicLogger.log;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import de.persosim.simulator.apdu.IsoSecureMessagingCommandApdu;
import de.persosim.simulator.apdu.ResponseApdu;
import de.persosim.simulator.cardobjects.CardObject;
import de.persosim.simulator.cardobjects.CardObjectUtils;
import de.persosim.simulator.cardobjects.DateTimeCardObject;
import de.persosim.simulator.cardobjects.MasterFile;
import de.persosim.simulator.cardobjects.TrustPointCardObject;
import de.persosim.simulator.cardobjects.TrustPointIdentifier;
import de.persosim.simulator.cardobjects.TypeIdentifier;
import de.persosim.simulator.crypto.CryptoUtil;
import de.persosim.simulator.crypto.certificates.CardVerifiableCertificate;
import de.persosim.simulator.crypto.certificates.CertificateExtension;
import de.persosim.simulator.crypto.certificates.ExtensionOid;
import de.persosim.simulator.crypto.certificates.PublicKeyReference;
import de.persosim.simulator.exception.CarParameterInvalidException;
import de.persosim.simulator.exception.CertificateNotParseableException;
import de.persosim.simulator.exception.CertificateUpdateException;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.protocols.AbstractProtocolStateMachine;
import de.persosim.simulator.protocols.GenericOid;
import de.persosim.simulator.protocols.Oid;
import de.persosim.simulator.protocols.SecInfoPublicity;
import de.persosim.simulator.secstatus.AuthorizationStore;
import de.persosim.simulator.secstatus.ConfinedAuthorizationMechanism;
import de.persosim.simulator.secstatus.EffectiveAuthorizationMechanism;
import de.persosim.simulator.secstatus.PaceMechanism;
import de.persosim.simulator.secstatus.SecMechanism;
import de.persosim.simulator.secstatus.SecStatus.SecContext;
import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation;
import de.persosim.simulator.tlv.Asn1;
import de.persosim.simulator.tlv.ConstructedTlvDataObject;
import de.persosim.simulator.tlv.PrimitiveTlvDataObject;
import de.persosim.simulator.tlv.TlvConstants;
import de.persosim.simulator.tlv.TlvDataObject;
import de.persosim.simulator.tlv.TlvDataObjectContainer;
import de.persosim.simulator.tlv.TlvTag;
import de.persosim.simulator.tlv.TlvValuePlain;
import de.persosim.simulator.utils.HexString;
import de.persosim.simulator.utils.Utils;
/**
* @author mboonk
*
* FIXME MBK add javadoc
*/
public abstract class AbstractTaProtocol extends AbstractProtocolStateMachine implements TlvConstants {
/*--------------------------------------------------------------------------------*/
public static final short P1P2_C1A4_SET_AT = (short) 0xC1A4;
public static final short P1P2_81B6_SET_DST = (short) 0x81B6;
public static final short P1P2_00BE_VERIFY_CERTIFICATE = (short) 0x00BE;
public static final short P1P2_0000_NO_FURTHER_INFORMATION = (short) 0x0000;
// values 0x00 - 0x3F are reserved for common COMMAND_X variables
public static final byte COMMAND_SET_DST = (byte) 0x40;
public static final byte COMMAND_EXTERNAL_AUTHENTICATE = (byte) 0x41;
public static final byte COMMAND_GET_CHALLENGE = (byte) 0x42;
public static final byte COMMAND_PSO = (byte) 0x43;
public static final byte APDU_SET_AT = 0;
public static final byte APDU_GET_NONCE = 1;
public static final byte APDU_MAP_NONCE = 2;
public static final byte APDU_PERFORM_KEY_AGREEMENT = 3;
public static final byte APDU_MUTUAL_AUTHENTICATE = 4;
public static final byte MASK_SFI_BYTE = (byte) 0x80;
private SecureRandom secureRandom = new SecureRandom();
private CardVerifiableCertificate currentCertificate;
private CardVerifiableCertificate mostRecentTemporaryCertificate;
private byte [] challenge;
private List<AuthenticatedAuxiliaryData> auxiliaryData;
private TaOid cryptographicMechanismReference;
private byte [] compressedTerminalEphemeralPublicKey;
private TrustPointCardObject trustPoint;
private TerminalType terminalType;
private byte[] firstSectorPublicKeyHash;
private byte[] secondSectorPublicKeyHash;
protected AuthorizationStore authorizationStore = null;
/*--------------------------------------------------------------------------------*/
public AbstractTaProtocol() {
super("TA");
}
/*--------------------------------------------------------------------------------*/
/**
* This method checks if the received APDU is a correct
* {@link IsoSecureMessagingCommandApdu} and was encrypted.
*
* @return true, iff it is a {@link IsoSecureMessagingCommandApdu} and the
* APDU was encrypted at some point in its history
*/
private boolean checkSecureMessagingApdu(){
if (processingData.getCommandApdu() instanceof IsoSecureMessagingCommandApdu){
if (!((IsoSecureMessagingCommandApdu) processingData
.getCommandApdu()).wasSecureMessaging()) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this,
"TA must be executed in secure messaging", resp);
return false;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6FFF_IMPLEMENTATION_ERROR);
this.processingData.updateResponseAPDU(this,
"This APDU should not have reached this point in processing, check for the correct APDU type processing in the APDU factory", resp);
return false;
}
return true;
}
protected void processCommandGetChallenge() {
if (!checkSecureMessagingApdu()){
return;
}
challenge = new byte [8];
secureRandom.nextBytes(challenge);
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(new TlvValuePlain(challenge), Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this,
"Command GetChallenge successfully processed", resp);
}
@Override
public void reset(){
super.reset();
authorizationStore = null;
currentCertificate = null;
mostRecentTemporaryCertificate = null;
auxiliaryData = null;
challenge = null;
}
void processCommandSetDst() {
if (!checkSecureMessagingApdu()){
return;
}
TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer();
TlvDataObject publicKeyReference = commandData.getTlvDataObject(TlvConstants.TAG_83);
if(publicKeyReference == null) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
this.processingData.updateResponseAPDU(this,"no public key reference found", resp);
return;
}
byte[] nameOfPublicKeyEncoded = publicKeyReference.getValueField();
//get necessary information stored in an earlier protocol (e.g. PACE)
HashSet<Class<? extends SecMechanism>> previousMechanisms = new HashSet<>();
previousMechanisms.add(PaceMechanism.class);
Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, previousMechanisms);
PaceMechanism paceMechanism = null;
if (currentMechanisms.size() == 1){
paceMechanism = (PaceMechanism) currentMechanisms.iterator().next();
// extract the currently used terminal type
try{
terminalType = TerminalType.getFromOid(paceMechanism.getOidForTa());
} catch (IllegalArgumentException e){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this, "Previous Pace protocol did not provide information about terminal type", resp);
return;
}
}
// reset the currently set key
currentCertificate = null;
// get the next certificate to verify against
if (mostRecentTemporaryCertificate != null && mostRecentTemporaryCertificate.getCertificateHolderReference() != null) {
// the temporary imported key is to be used
if (Arrays.equals(nameOfPublicKeyEncoded,
mostRecentTemporaryCertificate.getCertificateHolderReference().getBytes())) {
currentCertificate = mostRecentTemporaryCertificate;
}
}
if (currentCertificate != null){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this,
"Command SetDST successfully processed, public key found in temporary imported certificate", resp);
return;
}
String anchor = "";
// get the stored trust points
CardObject trustPointCandidate = CardObjectUtils.getSpecificChild(cardState.getMasterFile(), new TrustPointIdentifier(terminalType));
if (trustPointCandidate instanceof TrustPointCardObject) {
trustPoint = (TrustPointCardObject) trustPointCandidate;
if (trustPoint.getCurrentCertificate().getCertificateHolderReference() != null
&& Arrays.equals(trustPoint.getCurrentCertificate().getCertificateHolderReference().getBytes(), nameOfPublicKeyEncoded)) {
currentCertificate = trustPoint.getCurrentCertificate();
anchor = "first";
} else{
if (trustPoint.getPreviousCertificate().getCertificateHolderReference() != null
&& Arrays.equals(trustPoint.getPreviousCertificate().getCertificateHolderReference().getBytes(), nameOfPublicKeyEncoded)) {
currentCertificate = trustPoint.getPreviousCertificate();
anchor = "second";
}
}
if (currentCertificate != null){
// a new root certificate was selected
authorizationStore = getInitialAuthorizations(currentCertificate);
Authorization auth = null;
if (authorizationStore != null){
auth = authorizationStore.getAuthorization(terminalType.getAsOid());
}
if(auth == null) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this, "Previous protocol did not provide authorization information from chat", resp);
return;
}
}
}
if (currentCertificate != null){
updateAuthorizations(currentCertificate);
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this, "Command SetDST successfully processed, public key found in " + anchor + " trust anchor", resp);
return;
}
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
this.processingData.updateResponseAPDU(this,
"The identified public key could not be found in a trust point or temporarily imported certificate", resp);
return;
}
/**
* Get the authorization information that should be used as the initial
* authorization for the terminal authentication process. This method is
* called after choosing a new trust point.
*
* @param newRootCertificate
* the newly set trustpoint certificate
* @return the {@link AuthorizationStore} containing the initial access
* rights
*/
protected AuthorizationStore getInitialAuthorizations(CardVerifiableCertificate newRootCertificate){
//get necessary information stored in an earlier protocol (e.g. PACE)
HashSet<Class<? extends SecMechanism>> previousMechanisms = new HashSet<>();
previousMechanisms.add(ConfinedAuthorizationMechanism.class);
Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, previousMechanisms);
ConfinedAuthorizationMechanism authMechanism = null;
if (currentMechanisms.size() == 1){
authMechanism = (ConfinedAuthorizationMechanism) currentMechanisms.iterator().next();
if (authorizationStore == null) {
return authMechanism.getAuthorizationStore();
} else {
return authorizationStore;
}
}
return null;
}
public void updateAuthorizations(CardVerifiableCertificate certificate) {
authorizationStore.updateAuthorization(getAuthorizationsFromCertificate(certificate));
}
public HashMap<Oid, Authorization> getAuthorizationsFromCertificate(CardVerifiableCertificate certificate) {
HashMap<Oid, Authorization> authorizations = new HashMap<>();
CertificateHolderAuthorizationTemplate chat = certificate.getCertificateHolderAuthorizationTemplate();
RelativeAuthorization authFromChat = chat.getRelativeAuthorization();
authorizations.put(chat.getObjectIdentifier(), authFromChat);
return authorizations;
}
/**
* Checks if there are any known previous TA runs in the session.
*
* @return true if TA generally can be executed
*/
protected boolean isTaAllowed(){
Collection<Class<? extends SecMechanism>> wantedMechanisms = new HashSet<Class<? extends SecMechanism>>();
wantedMechanisms.add(TerminalAuthenticationMechanism.class);
Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, wantedMechanisms);
if (currentMechanisms.size() > 0){
return false;
} else {
return true;
}
}
void processCommandSetAt() {
if (!checkSecureMessagingApdu()){
return;
}
TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer();
TlvDataObject cryptographicMechanismReferenceData = commandData.getTlvDataObject(TlvConstants.TAG_80);
TlvDataObject publicKeyReferenceData = commandData.getTlvDataObject(TlvConstants.TAG_83);
TlvDataObject auxiliaryAuthenticatedData = commandData.getTlvDataObject(TlvConstants.TAG_67);
TlvDataObject ephemeralPublicKeyData = commandData.getTlvDataObject(TlvConstants.TAG_91);
if (publicKeyReferenceData != null){
try {
PublicKeyReference keyReference = new PublicKeyReference(publicKeyReferenceData);
if (!currentCertificate.getCertificateHolderReference().equals(keyReference)){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
this.processingData.updateResponseAPDU(this,
"The referenced public key could not be found", resp);
return;
}
} catch (CarParameterInvalidException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"The public key reference data is invalid", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"The public key reference data is missing", resp);
return;
}
if (cryptographicMechanismReferenceData != null){
//add missing Tag and Length
TlvDataObject cryptographicMechanismReferenceDataReconstructed = new PrimitiveTlvDataObject(TlvConstants.TAG_06, cryptographicMechanismReferenceData.getValueField());
try {
cryptographicMechanismReference = new TaOid(cryptographicMechanismReferenceDataReconstructed.getValueField());
} catch (IllegalArgumentException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"The cryptographic mechanism reference encoding is invalid", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
this.processingData.updateResponseAPDU(this,
"The public key reference data is missing", resp);
return;
}
if (auxiliaryAuthenticatedData != null){
if (auxiliaryAuthenticatedData instanceof ConstructedTlvDataObject){
auxiliaryData = new ArrayList<AuthenticatedAuxiliaryData>();
ConstructedTlvDataObject constructedAuxiliaryAuthenticatedData = (ConstructedTlvDataObject) auxiliaryAuthenticatedData;
for (TlvDataObject currentObject : constructedAuxiliaryAuthenticatedData.getTlvDataObjectContainer()){
if(!(currentObject instanceof ConstructedTlvDataObject) || !currentObject.getTlvTag().equals(TlvConstants.TAG_73)){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"Invalid encoding of the auxiliary data", resp);
return;
}
ConstructedTlvDataObject ddo = (ConstructedTlvDataObject) currentObject;
TlvDataObject objectIdentifier = ddo.getTlvDataObject(TlvConstants.TAG_06);
TlvDataObject discretionaryData = ddo.getTlvDataObject(TlvConstants.TAG_53);
try {
auxiliaryData.add(new AuthenticatedAuxiliaryData(new GenericOid(objectIdentifier.getValueField()), discretionaryData.getValueField()));
} catch (IllegalArgumentException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"Invalid encoding of the auxiliary data, object identifier not parseable", resp);
return;
}
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"Invalid encoding of the auxiliary data, authentication object is not constructed TLV", resp);
return;
}
}
if (ephemeralPublicKeyData != null){
compressedTerminalEphemeralPublicKey = ephemeralPublicKeyData.getValueField();
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
this.processingData.updateResponseAPDU(this,
"The ephemeral public key reference data is missing", resp);
return;
}
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this,
"Command SetAT successfully processed", resp);
}
/**
* This method checks the given data against the given RSA or ECDSA
* signature using the TA cryptographic mechanism reference OIDs.
*
* @param taOid
* defining the signature algorithm to be used
* @param publicKey
* to use for verification
* @param dataToVerify
* the raw data to be verified
* @param signatureData
* the signature data, in case of ECDSA the ASN1 signature
* structure is restored
* @return true, iff the signature can be verified against the given data
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchProviderException
*/
private boolean checkSignature(TaOid taOid, PublicKey publicKey, byte [] dataToVerify, byte [] signatureData) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, NoSuchProviderException{
log(this, "Verifying signature:");
Signature signature = taOid.getSignature();
if (signature != null){
signature.initVerify(publicKey);
signature.update(dataToVerify);
log(this, "Data to verify:\n" + HexString.dump(dataToVerify));
log(this, "Unprocessed signature data:\n" + HexString.dump(signatureData));
if (publicKey instanceof ECPublicKey){
signatureData = CryptoUtil.restoreAsn1SignatureStructure(signatureData).toByteArray();
}
log(this, "Processed signature data :\n" + HexString.dump(signatureData));
if(signature.verify(signatureData)){
log(this, "Verification OK");
return true;
}
} else {
log(this, "No signature found for OID");
}
log(this, "Verification failed");
return false;
}
void processCommandPsoVerifyCertificate() {
if (!checkSecureMessagingApdu()){
return;
}
TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer();
ConstructedTlvDataObject certificateBodyData = (ConstructedTlvDataObject) commandData.getTlvDataObject(TlvConstants.TAG_7F4E);
PrimitiveTlvDataObject certificateSignatureData = (PrimitiveTlvDataObject) commandData.getTlvDataObject(TlvConstants.TAG_5F37);
try {
ConstructedTlvDataObject certificateData = new ConstructedTlvDataObject(TlvConstants.TAG_7F21);
certificateData.addTlvDataObject(certificateBodyData, certificateSignatureData);
CardVerifiableCertificate certificate = new CardVerifiableCertificate(certificateData);
certificate.getPublicKey().updateKey(currentCertificate.getPublicKey());
if (certificate.getCertificationAuthorityReference().equals(currentCertificate.getCertificateHolderReference())){
if (!isCertificateIssuerValid(certificate, currentCertificate)){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE);
this.processingData.updateResponseAPDU(this,
"The certificate was issued by an invalid instance", resp);
return;
}
if (checkSignature((TaOid) currentCertificate.getBody().getPublicKey().getCvOid(), currentCertificate.getPublicKey(), certificateBodyData.toByteArray(), certificateSignatureData.getValueField())){
//differentiate between CVCA link certificates and other types for date validation
if (checkValidity(certificate, currentCertificate, getCurrentDate().getDate())){
try {
importCertificate(certificate, currentCertificate);
handleSuccessfulVerification(currentCertificate);
} catch (CertificateUpdateException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE);
this.processingData.updateResponseAPDU(this,
"Could not import the certificate", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE);
this.processingData.updateResponseAPDU(this,
"The certificate has an invalid date", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE);
this.processingData.updateResponseAPDU(this,
"Could not verify the certificate's signature", resp);
return;
}
} else {
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
this.processingData.updateResponseAPDU(this,
"Could not find fitting certificate (CAR invalid)", resp);
return;
}
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException | CertificateNotParseableException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE);
this.processingData.updateResponseAPDU(this,
"Could not verify the certificate", resp);
return;
}
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this,
"Command PSO Verify Certificate successfully processed", resp);
}
/**
* Checks the validity of a certificate against the current date. Expired
* CVCA link certificates are accepted, not yet effective certificates are
* also accepted. Terminal and DV certificates are checked to be not yet
* expired according to the chips date.
*
* @param certificate
* the certificate to check
* @param issuingCertificate
* the parent certificate in the chain to use for the check
* @param currentDate
* the date to check agains
* @return true, iff the certificate is valid as defined in TR-03110 v2.10
*/
protected static boolean checkValidity(CardVerifiableCertificate certificate, CardVerifiableCertificate issuingCertificate, Date currentDate) {
if (isCvcaCertificate(issuingCertificate)){
if (isCvcaCertificate(certificate)){
// the issuing certificate is allowed to be expired to allow import of a link certificate
return true;
} else {
// for terminal and dv certificates the issuing cvca and the certificate itself must be valid (not yet expired)
if (!currentDate.after(issuingCertificate.getExpirationDate()) && !currentDate.after(certificate.getExpirationDate())){
return true;
}
}
} else {
//check only the date of the given certificate, at this point the cvca has already been verified
if (currentDate.before(certificate.getExpirationDate()) || currentDate.equals(certificate.getExpirationDate())){
return true;
}
}
return false;
}
/**
* Check the given certificates for compliance to the definitions in
* TR-03110 v2.10 2.6.2
*
* @param certificate
* to check
* @param certificateToCheckAgainst
* (normally the preceding certificate in the chain)
* @return true, iff the conditions are fulfilled
* @throws CertificateNotParseableException
*/
protected static boolean isCertificateIssuerValid(CardVerifiableCertificate certificate,
CardVerifiableCertificate certificateToCheckAgainst) throws CertificateNotParseableException {
if ((isCvcaCertificate(certificate) || isDvCertificate(certificate)) && !isCvcaCertificate(certificateToCheckAgainst)){
return false;
}
if (isTerminalCertificate(certificate) && !isDvCertificate(certificateToCheckAgainst)){
return false;
}
return true;
}
/**
* Update a date object using the given certificates as described in
* TR-03110 v2.10 2.6.2
*
* @param certificate
* to extract the new date from
* @param issuingCertificate
* issuer of the certificate given in the first parameter, this
* is not checked
* @param currentDate
* the {@link DateTimeCardObject} to store the certificates date
* in
*/
protected static void updateDate(CardVerifiableCertificate certificate, CardVerifiableCertificate issuingCertificate, DateTimeCardObject currentDate) {
if (currentDate.getDate().before((certificate.getEffectiveDate()))){
if (isCvcaCertificate(certificate) || isDomesticDvCertificate(certificate)
|| isDomesticDvCertificate(issuingCertificate)){
currentDate.update(certificate.getEffectiveDate());
}
}
}
/**
* @return the currently stored date
*/
private DateTimeCardObject getCurrentDate(){
return (DateTimeCardObject) CardObjectUtils.getSpecificChild(cardState.getMasterFile(), new TypeIdentifier(DateTimeCardObject.class));
}
/**
* This method does not check the certificates validity.
* @param certificate to check
* @return true, iff the certificate is a domestic DV certificate
*/
private static boolean isDomesticDvCertificate(CardVerifiableCertificate certificate) {
return certificate.getCertificateHolderAuthorizationTemplate().getRelativeAuthorization().getRole().equals(CertificateRole.DV_TYPE_1);
}
/**
* @param certificate
* @return true, iff the given certificate uses one of the DV {@link CertificateRole}s
*/
private static boolean isDvCertificate(CardVerifiableCertificate certificate) {
return certificate.getCertificateHolderAuthorizationTemplate()
.getRelativeAuthorization().getRole()
.equals(CertificateRole.DV_TYPE_1)
|| certificate.getCertificateHolderAuthorizationTemplate()
.getRelativeAuthorization().getRole()
.equals(CertificateRole.DV_TYPE_2);
}
/**
* @param certificate
* @return true, iff the given certificate uses the {@link CertificateRole#CVCA}
*/
private static boolean isCvcaCertificate(CardVerifiableCertificate certificate) {
return certificate.getCertificateHolderAuthorizationTemplate()
.getRelativeAuthorization().getRole()
.equals(CertificateRole.CVCA);
}
/**
* This method imports the given certificate without further checks.
* @param certificate
* @param issuingCertificate
* @throws CertificateUpdateException
*/
private void importCertificate(CardVerifiableCertificate certificate, CardVerifiableCertificate issuingCertificate) throws CertificateUpdateException {
updateDate(certificate, issuingCertificate, getCurrentDate());
if (isCvcaCertificate(certificate)) {
permanentImport(certificate);
} else if (isDvCertificate(certificate)
|| isTerminalCertificate(certificate)) {
temporaryImport(certificate);
}
}
/**
* Perform the permanent import of a certificate as described in TR-03110 v2.10 A.6.2.1.
* @param certificate
* @throws CertificateUpdateException
*/
private void permanentImport(CardVerifiableCertificate certificate) throws CertificateUpdateException {
if (trustPoint != null){
//trustPoint.updateTrustpoint(certificate.getCertificateHolderReference(), certificate);
trustPoint.updateTrustpoint(certificate);
}
}
/**
* @param certificate
* @return true, iff the given certificate uses the {@link CertificateRole#TERMINAL}
*/
public static boolean isTerminalCertificate(CardVerifiableCertificate certificate) {
return certificate.getCertificateHolderAuthorizationTemplate()
.getRelativeAuthorization().getRole()
.equals(CertificateRole.TERMINAL);
}
/**
* Perform the temporary import of a certificate as described in TR-03110 v2.10 A.6.2.2.
* @param certificate
*/
private void temporaryImport(CardVerifiableCertificate certificate) {
mostRecentTemporaryCertificate = certificate;
currentCertificate = mostRecentTemporaryCertificate;
}
void processCommandExternalAuthenticate() {
if (processingData.getCommandApdu() instanceof IsoSecureMessagingCommandApdu
&& !((IsoSecureMessagingCommandApdu) processingData
.getCommandApdu()).wasSecureMessaging()) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this,
"TA must be executed in secure messaging", resp);
return;
}
//ensure GetChallenge was called before
if (challenge == null) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this,"No challenge was generated, please call GetChallenge first", resp);
return;
}
if (!isTaAllowed()){
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this,
"TA execution is not allowed", resp);
return;
}
byte [] terminalSignatureData = processingData.getCommandApdu().getCommandData().toByteArray();
//get necessary information stored in an earlier protocol (e.g. PACE)
HashSet<Class<? extends SecMechanism>> previousMechanisms = new HashSet<>();
previousMechanisms.add(PaceMechanism.class);
Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, previousMechanisms);
if (currentMechanisms.size() > 0){
PaceMechanism paceMechanism = (PaceMechanism) currentMechanisms.toArray()[0];
byte [] idPicc = paceMechanism.getCompressedEphemeralPublicKey();
byte [] dataToVerify = Utils.concatByteArrays(idPicc, challenge, compressedTerminalEphemeralPublicKey);
if (auxiliaryData != null && auxiliaryData.size() > 0){
ConstructedTlvDataObject auxiliaryDataTlv = new ConstructedTlvDataObject(TlvConstants.TAG_67);
for(AuthenticatedAuxiliaryData current : auxiliaryData){
auxiliaryDataTlv.addTlvDataObject(current.getEncoded());
}
dataToVerify = Utils.concatByteArrays(dataToVerify, auxiliaryDataTlv.toByteArray());
}
try {
if (checkSignature(cryptographicMechanismReference, currentCertificate.getPublicKey() , dataToVerify, terminalSignatureData)){
handleSuccessfulTerminalAuthentication(currentCertificate);
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6300_AUTHENTICATION_FAILED);
this.processingData.updateResponseAPDU(this,"The signature could not be verified", resp);
return;
}
} catch (InvalidKeyException | NoSuchAlgorithmException
| SignatureException | NoSuchProviderException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6FFF_IMPLEMENTATION_ERROR);
this.processingData.updateResponseAPDU(this,"The signature could not be verified", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED);
this.processingData.updateResponseAPDU(this,"No protocol providing data for ID_PICC calculation was run", resp);
}
}
protected List<CertificateExtension> extractExtensions(CardVerifiableCertificate certificate) {
return certificate.getCertificateExtensions();
}
/**
* This methods handles a successful terminal authentication and sets the
* protocol state accordingly.
*
* @param verifiedTerminalCertificate
*/
protected void handleSuccessfulTerminalAuthentication(CardVerifiableCertificate verifiedTerminalCertificate) {
List<CertificateExtension> certificateExtensions = extractExtensions(currentCertificate);
extractTerminalSector(verifiedTerminalCertificate);
TerminalAuthenticationMechanism mechanism = new TerminalAuthenticationMechanism(compressedTerminalEphemeralPublicKey, terminalType, auxiliaryData, firstSectorPublicKeyHash, secondSectorPublicKeyHash, cryptographicMechanismReference.getHashAlgorithmName(), certificateExtensions);
processingData.addUpdatePropagation(this, "Updated security status with terminal authentication information", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, mechanism));
EffectiveAuthorizationMechanism authMechanism = new EffectiveAuthorizationMechanism(authorizationStore);
processingData.addUpdatePropagation(this, "Updated security status with terminal authentication information", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanism));
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
this.processingData.updateResponseAPDU(this,
"Command External Authenticate successfully processed", resp);
}
/**
* This method handles the internal state changes caused by successfully
* verified certificates.
*/
protected void handleSuccessfulVerification(CardVerifiableCertificate verifiedCertificate) {
updateAuthorizations(verifiedCertificate);
}
/**
* Extract the terminal sector information from the current certificate.
* This method should be called on terminal certificates.
*
* @param certificate
*/
private void extractTerminalSector(CardVerifiableCertificate certificate) {
for(CertificateExtension extension : certificate.getCertificateExtensions()){
if (extension.getObjectIdentifier().equals(ExtensionOid.id_Sector)){
if (extension.getDataObjects().containsTlvDataObject(TlvConstants.TAG_80)){
firstSectorPublicKeyHash = extension.getDataObjects().getTlvDataObject(TlvConstants.TAG_80).getValueField();
}
if (extension.getDataObjects().containsTlvDataObject(TlvConstants.TAG_81)){
secondSectorPublicKeyHash = extension.getDataObjects().getTlvDataObject(TlvConstants.TAG_81).getValueField();
}
}
}
}
@Override
public Collection<TlvDataObject> getSecInfos(SecInfoPublicity publicity, MasterFile mf) {
// TAInfo
ConstructedTlvDataObject taInfo = new ConstructedTlvDataObject(
new TlvTag(Asn1.SEQUENCE));
PrimitiveTlvDataObject protocol = new PrimitiveTlvDataObject(
new TlvTag(Asn1.OBJECT_IDENTIFIER),
new TlvValuePlain(HexString
.toByteArray("04 00 7F 00 07 02 02 02")));
PrimitiveTlvDataObject version = new PrimitiveTlvDataObject(new TlvTag(Asn1.INTEGER),
new TlvValuePlain(new byte[] { 2 }));
taInfo.addTlvDataObject(protocol);
taInfo.addTlvDataObject(version);
Collection<TlvDataObject> result = new HashSet<>();
result.add(taInfo);
return result;
}
}