package de.persosim.simulator.protocols.ri;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import javax.crypto.KeyAgreement;
import org.globaltester.cryptoprovider.Crypto;
import org.globaltester.logging.InfoSource;
import de.persosim.simulator.apdu.ResponseApdu;
import de.persosim.simulator.apdumatching.ApduSpecificationConstants;
import de.persosim.simulator.cardobjects.CardObject;
import de.persosim.simulator.cardobjects.CardObjectIdentifier;
import de.persosim.simulator.cardobjects.CardObjectUtils;
import de.persosim.simulator.cardobjects.KeyIdentifier;
import de.persosim.simulator.cardobjects.KeyPairObject;
import de.persosim.simulator.cardobjects.MasterFile;
import de.persosim.simulator.cardobjects.OidIdentifier;
import de.persosim.simulator.crypto.StandardizedDomainParameters;
import de.persosim.simulator.exception.VerificationException;
import de.persosim.simulator.platform.CardStateAccessor;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.platform.PlatformUtil;
import de.persosim.simulator.processing.ProcessingData;
import de.persosim.simulator.protocols.GenericOid;
import de.persosim.simulator.protocols.Oid;
import de.persosim.simulator.protocols.Protocol;
import de.persosim.simulator.protocols.RoleOid;
import de.persosim.simulator.protocols.SecInfoPublicity;
import de.persosim.simulator.protocols.ta.Authorization;
import de.persosim.simulator.protocols.ta.TerminalAuthenticationMechanism;
import de.persosim.simulator.protocols.ta.TerminalType;
import de.persosim.simulator.secstatus.EffectiveAuthorizationMechanism;
import de.persosim.simulator.secstatus.SecMechanism;
import de.persosim.simulator.secstatus.SecStatus.SecContext;
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.utils.Utils;
/**
* This class implements the restricted identification protocol as described in
* TR-03110 v2.10 Part 2.
*
* @author mboonk
*
*/
public class RiProtocol implements Protocol, Iso7816, ApduSpecificationConstants,
InfoSource, Ri, TlvConstants {
private CardStateAccessor cardState;
private int privateKeyReference;
private KeyPairObject staticKeyObject;
public RiProtocol() {
reset();
}
@Override
public String getProtocolName() {
return "RI";
}
@Override
public void setCardStateAccessor(CardStateAccessor cardState) {
this.cardState = cardState;
}
@Override
public Collection<TlvDataObject> getSecInfos(SecInfoPublicity publicity, MasterFile mf) {
if ((publicity == SecInfoPublicity.AUTHENTICATED) || (publicity == SecInfoPublicity.PRIVILEGED)) {
OidIdentifier riOidIdentifier = new OidIdentifier(new GenericOid(Ri.id_RI));
Collection<CardObject> riKeyCardObjects = mf.findChildren(
new KeyIdentifier(), riOidIdentifier);
HashSet<TlvDataObject> secInfos = new HashSet<TlvDataObject>();
for (CardObject curCardObject : riKeyCardObjects) {
if (! (curCardObject instanceof KeyPairObject)) {
continue;
}
KeyPairObject curKey = (KeyPairObject) curCardObject;
Collection<CardObjectIdentifier> identifiers = curKey.getAllIdentifiers();
//extract keyId
int keyId = -1;
for (CardObjectIdentifier curIdentifier : identifiers) {
if (curIdentifier instanceof KeyIdentifier) {
keyId = ((KeyIdentifier) curIdentifier).getKeyReference();
break;
}
}
if (keyId == -1) continue; // skip keys that dont't provide a keyId
//cached values
byte[] genericRiOidBytes = null;
//construct and add RiInfos
for (CardObjectIdentifier curIdentifier : identifiers) {
if (curIdentifier instanceof OidIdentifier) {
Oid curOid = ((OidIdentifier) curIdentifier).getOid();
if (curOid.startsWithPrefix(id_RI)) {
byte[] oidBytes = curOid.toByteArray();
genericRiOidBytes = Arrays.copyOfRange(oidBytes, 0, 9);
/*
* ProtocolParams ::= SEQUENCE {
* version INTEGER, -- MUST be 1
* keyId INTEGER,
* authorizedOnly BOOLEAN
* }
*/
ConstructedTlvDataObject params = new ConstructedTlvDataObject(TAG_SEQUENCE);
params.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, new byte[]{1}));
params.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, new byte[]{(byte) keyId}));
// add "authorizedOnly"
if(curKey.isPrivilegedOnly()) {
params.addTlvDataObject(new PrimitiveTlvDataObject(TAG_BOOLEAN, DER_BOOLEAN_TRUE)); //IMPL RI handle authorizedOnly
} else {
params.addTlvDataObject(new PrimitiveTlvDataObject(TAG_BOOLEAN, DER_BOOLEAN_FALSE));
}
// define RestrictedIdentificationInfo
/*
* RestrictedIdentificationInfo ::= SEQUENCE {
* protocol OBJECT IDENTIFIER(
* id-RI-DH-SHA-1 |
* id-RI-DH-SHA-224 |
* id-RI-DH-SHA-256 |
* id-RI-DH-SHA-384 |
* id-RI-DH-SHA-512 |
* id-RI-ECDH-SHA-1 |
* id-RI-ECDH-SHA-224 |
* id-RI-ECDH-SHA-256 |
* id-RI-ECDH-SHA-384 |
* id-RI-ECDH-SHA-512),
* params ProtocolParams,
* maxKeyLen INTEGER OPTIONAL
* }
*/
ConstructedTlvDataObject riInfo = new ConstructedTlvDataObject(TAG_SEQUENCE);
riInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, oidBytes));
riInfo.addTlvDataObject(params);
//IMPL RI handle maxKeyLen
// riInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_INTEGER, new byte[]{xxx}));
secInfos.add(riInfo);
}
}
}
//extract required data from curKey
KeyPair curKeyPair = curKey.getKeyPair();
ConstructedTlvDataObject encKey = new ConstructedTlvDataObject(curKeyPair.getPublic().getEncoded());
ConstructedTlvDataObject algIdentifier = (ConstructedTlvDataObject) encKey.getTlvDataObject(TAG_SEQUENCE);
//using standardized domain parameters if possible
algIdentifier = StandardizedDomainParameters.simplifyAlgorithmIdentifier(algIdentifier);
/*
* RestrictedIdentificationDomainParameterInfo ::= SEQUENCE {
* protocol OBJECT IDENTIFIER(id-RI-DH | id-RI-ECDH),
* domainParameter AlgorithmIdentifier
* }
*/
ConstructedTlvDataObject riDomainInfo = new ConstructedTlvDataObject(TAG_SEQUENCE);
riDomainInfo.addTlvDataObject(new PrimitiveTlvDataObject(TAG_OID, genericRiOidBytes));
riDomainInfo.addTlvDataObject(algIdentifier);
secInfos.add(riDomainInfo);
}
return secInfos;
} else {
return Collections.emptySet();
}
}
@Override
public void process(ProcessingData processingData) {
if (processingData.getCommandApdu().getIns() == INS_22_MANAGE_SECURITY_ENVIRONMENT && processingData.getCommandApdu().getP1() == (byte) 0x41) {
processCommandSetAt(processingData);
} else if (processingData.getCommandApdu().getIns() == INS_86_GENERAL_AUTHENTICATE) {
processCommandGeneralAuthenticate(processingData);
}
}
/**
* This method checks the public key against the hash that was transmitted
* as a part of a previously executed terminal authentication.
*
* @param sectorPublicKey
* the data object containing the public key as transmitted
* @param hash
* the {@link MessageDigest} to use for checking
* @param sectorPublicKeyHash
* the hash to check against
* @return true, iff the hashes are equal
*/
private boolean checkSectorPublicKeyHash(ConstructedTlvDataObject sectorPublicKey, MessageDigest hash, byte [] sectorPublicKeyHash){
ConstructedTlvDataObject temp = new ConstructedTlvDataObject(TlvConstants.TAG_7F49);
for (TlvDataObject object : sectorPublicKey.getTlvDataObjectContainer()){
temp.addTlvDataObject(object);
}
return Arrays.equals(sectorPublicKeyHash, hash.digest(temp.toByteArray()));
}
/**
* This method performs the calculation of the sector identifier as
* described in TR-03110 v2.10 Part 2 3.5
*
* @param sectorPublicKey
* the public key transmitted by the terminal
* @param keyAgreement
* mechanism to use
* @param hash
* {@link MessageDigest} to use
* @return the sector identifier as byte array or null in case of errors
*/
private byte[] calculateSectorIdentifier(PrivateKey staticPrivateKey, PublicKey sectorPublicKey,
KeyAgreement keyAgreement, MessageDigest hash) {
byte[] result;
// perform key agreement
try {
keyAgreement.init(staticPrivateKey);
keyAgreement.doPhase(sectorPublicKey, true);
result = hash.digest(keyAgreement.generateSecret());
} catch (InvalidKeyException e) {
result = null;
}
return result;
}
/**
* This method handles the complete sector identifier processing, including
* extraction from the tlv data.
*
* @param commandTag
* the {@link TlvTag} that was used to transmit the public key
* @param staticPrivateKey
* the private key to calculate the sector identifier with
* @param dynamicAuthenticationData
* the tlv data containing the public key
* @param publicKeyCheckingHash
* the {@link MessageDigest} to use for checking the public key
* hash
* @param sectorPublicKeyHash
* the previously transmitted hash of the public key
* @param responseTag
* the {@link TlvTag} to use for the response data object
* @return the {@link PrimitiveTlvDataObject} that contains the sector
* identifier
* @throws GeneralSecurityException
* @throws VerificationException
*/
private PrimitiveTlvDataObject handleSectorKey(TlvTag commandTag, PrivateKey staticPrivateKey, ConstructedTlvDataObject dynamicAuthenticationData, MessageDigest publicKeyCheckingHash, byte [] sectorPublicKeyHash, TlvTag responseTag) throws GeneralSecurityException, VerificationException{
TlvDataObject sectorPublicKeyData = dynamicAuthenticationData.getTlvDataObject(commandTag);
if (sectorPublicKeyData instanceof ConstructedTlvDataObject) {
if (!checkSectorPublicKeyHash((ConstructedTlvDataObject)sectorPublicKeyData, publicKeyCheckingHash, sectorPublicKeyHash)){
throw new VerificationException("The public key hash transmitted during a previous protocol does not match the given public key");
}
RiOid oid = new RiOid(((ConstructedTlvDataObject)sectorPublicKeyData).getTlvDataObject(TlvConstants.TAG_06).getValueField());
PublicKey sectorPublicKey = oid.parsePublicKey((ConstructedTlvDataObject) sectorPublicKeyData);
return new PrimitiveTlvDataObject(responseTag, calculateSectorIdentifier(staticPrivateKey, sectorPublicKey, oid.getKeyAgreement(), oid.getHash()));
}
return null;
}
private void processCommandGeneralAuthenticate(ProcessingData processingData) {
if (processingData.getCommandApdu().getCommandDataObjectContainer().getTlvDataObject(TlvConstants.TAG_7C) instanceof ConstructedTlvDataObject){
ConstructedTlvDataObject dynamicAuthenticationData = (ConstructedTlvDataObject)processingData.getCommandApdu()
.getCommandDataObjectContainer().getTlvDataObject(TlvConstants.TAG_7C);
//get necessary information stored in TA
HashSet<Class<? extends SecMechanism>> previousMechanisms = new HashSet<>();
previousMechanisms.add(TerminalAuthenticationMechanism.class);
previousMechanisms.add(EffectiveAuthorizationMechanism.class);
Collection<SecMechanism> currentMechanisms = cardState.getCurrentMechanisms(SecContext.APPLICATION, previousMechanisms);
TerminalAuthenticationMechanism taMechanism = null;
EffectiveAuthorizationMechanism authMechanism = null;
if (currentMechanisms.size() >= 2){
for(SecMechanism secmechanism:currentMechanisms) {
if(secmechanism instanceof TerminalAuthenticationMechanism) {
taMechanism = (TerminalAuthenticationMechanism) secmechanism;
}
if(secmechanism instanceof EffectiveAuthorizationMechanism) {
authMechanism = (EffectiveAuthorizationMechanism) secmechanism;
}
}
if((taMechanism == null) || (authMechanism == null)) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED);
processingData.updateResponseAPDU(this, "Restricted Identification only allowed for Authentication Terminals", resp);
return;
}
Authorization auth = authMechanism.getAuthorization(RoleOid.id_AT);
if (!(taMechanism.getTerminalType().equals(TerminalType.AT))) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED);
processingData.updateResponseAPDU(this, "Restricted Identification only allowed for Authentication Terminals", resp);
return;
}
byte [] firstSectorPublicKeyHash = taMechanism.getFirstSectorPublicKeyHash();
byte [] secondSectorPublicKeyHash = taMechanism.getSecondSectorPublicKeyHash();
MessageDigest publicKeyCheckingHash;
try {
publicKeyCheckingHash = MessageDigest.getInstance(taMechanism.getSectorPublicKeyHashAlgorithm(), Crypto.getCryptoProvider());
} catch (GeneralSecurityException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6FFF_IMPLEMENTATION_ERROR);
processingData.updateResponseAPDU(this,
"The hash algorithm for checking the public key could not be instantiated", resp);
return;
}
if (staticKeyObject == null){
//{@link staticKeyObject} may be null either if setAt has not yet been executed or failed.
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(PlatformUtil.SW_4A80_WRONG_DATA);
processingData.updateResponseAPDU(this,
"The static key pair was not set correctly, probably due to missing or failed execution of setAT command", resp);
return;
}
KeyPair staticKeyPair = staticKeyObject.getKeyPair();
// check for Restricted Identification bit
if(staticKeyObject.isPrivilegedOnly()) {
if ((auth == null) || (!(auth.getAuthorization().getBit(2)))) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED);
processingData.updateResponseAPDU(this, "Restricted Identification only allowed for authorized terminals", resp);
return;
}
}
PrivateKey staticPrivateKey = staticKeyPair.getPrivate();
ConstructedTlvDataObject responseData = new ConstructedTlvDataObject(
TlvConstants.TAG_7C);
try {
if (dynamicAuthenticationData.getTlvDataObject(RI_FIRST_SECTOR_KEY_TAG) != null) {
responseData.addTlvDataObject(handleSectorKey(
RI_FIRST_SECTOR_KEY_TAG,
staticPrivateKey,
dynamicAuthenticationData,
publicKeyCheckingHash,
firstSectorPublicKeyHash,
TlvConstants.TAG_81));
}
if (dynamicAuthenticationData.getTlvDataObject(RI_SECOND_SECTOR_KEY_TAG) != null) {
responseData.addTlvDataObject(handleSectorKey(
RI_SECOND_SECTOR_KEY_TAG,
staticPrivateKey,
dynamicAuthenticationData,
publicKeyCheckingHash,
secondSectorPublicKeyHash,
TlvConstants.TAG_83));
}
} catch (GeneralSecurityException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
processingData.updateResponseAPDU(this,
"no sector identifiers could be calculated because of errors using the public key", resp);
return;
} catch (VerificationException e) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
processingData.updateResponseAPDU(this,
"the given public key is invalid", resp);
}
if (responseData.getNoOfElements() > 0) {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(new TlvDataObjectContainer(responseData), Iso7816.SW_9000_NO_ERROR);
processingData.updateResponseAPDU(this,
"Restricted identification successfully executed", resp);
return;
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6A80_WRONG_DATA);
processingData.updateResponseAPDU(this,
"no sector identifiers could be calculated becaus of missing or incorrect input data", resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED);
processingData.updateResponseAPDU(this, "Restricted Identification requires preceding Terminal Authentication", resp);
}
}
}
private void processCommandSetAt(ProcessingData processingData) {
TlvDataObject cryptographicMechanismReferenceData = processingData
.getCommandApdu().getCommandDataObjectContainer()
.getTlvDataObject(TlvConstants.TAG_80);
TlvDataObject privateKeyReferenceData = processingData.getCommandApdu()
.getCommandDataObjectContainer()
.getTlvDataObject(TlvConstants.TAG_84);
if (cryptographicMechanismReferenceData != null) {
try{
new RiOid(
cryptographicMechanismReferenceData.getValueField());
} catch (IllegalArgumentException e){
ResponseApdu resp = new ResponseApdu(PlatformUtil.SW_4A80_WRONG_DATA);
processingData.updateResponseAPDU(this,
"The cryptographic mechanism reference data is missing",
resp);
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
processingData.updateResponseAPDU(this,
"The cryptographic mechanism reference data is missing",
resp);
return;
}
if (privateKeyReferenceData != null) {
privateKeyReference = Utils
.getIntFromUnsignedByteArray(privateKeyReferenceData
.getValueField());
KeyIdentifier keyIdentifier = new KeyIdentifier(privateKeyReference);
CardObject cardObject = CardObjectUtils.findObject(cardState.getMasterFile(), keyIdentifier);
if ((cardObject instanceof KeyPairObject)) {
staticKeyObject = (KeyPairObject) cardObject;
} else {
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
processingData.updateResponseAPDU(this,
"invalid key reference", resp);
/* there is nothing more to be done here */
return;
}
} else {
// create and propagate response APDU
ResponseApdu resp = new ResponseApdu(
Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND);
processingData.updateResponseAPDU(this,
"The private key reference was not found", resp);
return;
}
//create and propagate response APDU
ResponseApdu resp = new ResponseApdu(Iso7816.SW_9000_NO_ERROR);
processingData.updateResponseAPDU(this, "Command SetAt successfully processed", resp);
}
@Override
public String getIDString() {
return "Restricted Identification";
}
@Override
public void reset() {
privateKeyReference = -1;
}
@Override
public boolean isMoveToStackRequested() {
return false;
}
}