package de.persosim.simulator.protocols.pace;
import static org.globaltester.logging.BasicLogger.DEBUG;
import static org.globaltester.logging.BasicLogger.log;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import org.globaltester.logging.InfoSource;
import de.persosim.simulator.apdu.CommandApdu;
import de.persosim.simulator.apdu.CommandApduFactory;
import de.persosim.simulator.apdu.IsoSecureMessagingCommandApdu;
import de.persosim.simulator.apdu.ResponseApdu;
import de.persosim.simulator.apdu.SmMarkerApdu;
import de.persosim.simulator.apdumatching.ApduSpecificationConstants;
import de.persosim.simulator.cardobjects.AuthObjectIdentifier;
import de.persosim.simulator.cardobjects.CardObject;
import de.persosim.simulator.cardobjects.CardObjectUtils;
import de.persosim.simulator.cardobjects.MasterFile;
import de.persosim.simulator.cardobjects.PasswordAuthObject;
import de.persosim.simulator.cardobjects.PasswordAuthObjectWithRetryCounter;
import de.persosim.simulator.cardobjects.TrustPointCardObject;
import de.persosim.simulator.cardobjects.TrustPointIdentifier;
import de.persosim.simulator.crypto.certificates.PublicKeyReference;
import de.persosim.simulator.platform.CardStateAccessor;
import de.persosim.simulator.platform.Iso7816;
import de.persosim.simulator.platform.Iso7816Lib;
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.ProtocolUpdate;
import de.persosim.simulator.protocols.ResponseData;
import de.persosim.simulator.protocols.SecInfoPublicity;
import de.persosim.simulator.protocols.ta.Authorization;
import de.persosim.simulator.protocols.ta.CertificateHolderAuthorizationTemplate;
import de.persosim.simulator.protocols.ta.CertificateRole;
import de.persosim.simulator.protocols.ta.RelativeAuthorization;
import de.persosim.simulator.protocols.ta.TerminalType;
import de.persosim.simulator.secstatus.AuthorizationStore;
import de.persosim.simulator.secstatus.ConfinedAuthorizationMechanism;
import de.persosim.simulator.secstatus.PaceMechanism;
import de.persosim.simulator.secstatus.SecStatus;
import de.persosim.simulator.secstatus.SecStatus.SecContext;
import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation;
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.TlvValue;
import de.persosim.simulator.utils.BitField;
import de.persosim.simulator.utils.HexString;
import de.persosim.simulator.utils.Utils;
/**
* In order to simplify implementation of PACE within
* de.persosim.driver.connector no real PACE is performed but only a bypassed
* version. This bypassed version interfaces with this protocol which ensures
* the pseude SM handling as well as the correct contents of the
* {@link SecStatus}.
* <p/>
* Essentially a pseudo APDU initiates the new pace bypass sm session. This
* carries all required data as provided in MSE SetAT and the selected password
* in plain. The password is verified directly and if it matches an according
* pseudo SM channel is setup.
* <p/>
* The pseudo SM are just normal APDUS with lowest two bytes of CLA set (as no
* channels are supported this is sufficient)
*
* @author amay
*
*/
//XXX reduce code duplication with AbstractPaceProtocol
public class PaceBypassProtocol implements Pace, Protocol, Iso7816, ApduSpecificationConstants,
InfoSource, TlvConstants {
private CardStateAccessor cardState;
private boolean pseudoSmIsActive = false;
/*
* Move to stack relies on the order processingData is processed. If the
* protocol is already on the stack it is known that the ProcessingData will
* be seen at least twice before {@link #isMoveToStackRequested()} is
* called. This checking is implemented at the beginning of #process and
* results are stored in the following two variables.
*/
private boolean moveToStack = true;
private ProcessingData lastSeenProcessingData = null;
public PaceBypassProtocol() {
reset();
}
@Override
public String getProtocolName() {
return "PaceBypass";
}
@Override
public void setCardStateAccessor(CardStateAccessor cardState) {
this.cardState = cardState;
}
@Override
public Collection<TlvDataObject> getSecInfos(SecInfoPublicity publicity, MasterFile mf) {
//no own SecInfos needed, simply support those configured by the actual PaceProtocol
return Collections.emptySet();
}
@Override
public void process(ProcessingData processingData) {
//check whether this processingData has been seen before
if (processingData == lastSeenProcessingData) {
moveToStack = false;
} else {
moveToStack = true;
lastSeenProcessingData = processingData;
}
byte cla = processingData.getCommandApdu().getCla();
byte ins = processingData.getCommandApdu().getIns();
if (cla == (byte) 0xff && ins == INS_86_GENERAL_AUTHENTICATE) {
processInitPaceBypass(processingData);
} else {
processSm(processingData);
}
}
/**
* Try to initiate a Pace Bypass
* <p>
*
*/
private void processInitPaceBypass(ProcessingData processingData) {
//prepare the response data
TlvDataObjectContainer responseObjects = new TlvDataObjectContainer();
short sw = Iso7816.SW_9000_NO_ERROR;
String note = "";
//get commandDataContainer
TlvDataObjectContainer commandData = processingData.getCommandApdu().getCommandDataObjectContainer();
// PACE password id
PasswordAuthObject passwordObject = null;
TlvDataObject tlvObject = commandData.getTlvDataObject(TAG_83);
CardObject pwdCandidate = CardObjectUtils.getSpecificChild(cardState.getMasterFile(), new AuthObjectIdentifier(tlvObject.getValueField()));
if (pwdCandidate instanceof PasswordAuthObject){
passwordObject = (PasswordAuthObject) pwdCandidate;
log(this, "selected password is: " + AbstractPaceProtocol.getPasswordName(passwordObject.getPasswordIdentifier()), DEBUG);
} else {
sw = Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND;
note = "no fitting authentication object found";
}
// provided password
byte[] providedPassword = null;
tlvObject = commandData.getTlvDataObject(TAG_92);
if (tlvObject != null) {
providedPassword = tlvObject.getValueField();
} else {
if (sw == Iso7816.SW_9000_NO_ERROR) {
sw = Iso7816.SW_6A80_WRONG_DATA;
note = "no password provided";
}
}
//extract CHAT
CertificateHolderAuthorizationTemplate usedChat = null;
TrustPointCardObject trustPoint = null;
tlvObject = commandData.getTlvDataObject(TAG_7F4C);
if (tlvObject != null){
ConstructedTlvDataObject chatData = (ConstructedTlvDataObject) tlvObject;
TlvDataObject oidData = chatData.getTlvDataObject(TAG_06);
byte[] roleData = chatData.getTlvDataObject(TAG_53).getValueField();
Oid chatOid = new GenericOid(oidData.getValueField());
RelativeAuthorization authorization = new RelativeAuthorization(
CertificateRole.getFromMostSignificantBits(roleData[0]), BitField.buildFromBigEndian(
(roleData.length * 8) - 2, roleData));
usedChat = new CertificateHolderAuthorizationTemplate(chatOid, TerminalType.getFromOid(chatOid),
authorization);
TerminalType terminalType = usedChat.getTerminalType();
trustPoint = (TrustPointCardObject) CardObjectUtils.getSpecificChild(cardState.getMasterFile(),
new TrustPointIdentifier(terminalType));
if (!AbstractPaceProtocol.checkPasswordAndAccessRights(usedChat, passwordObject)){
if (sw == Iso7816.SW_9000_NO_ERROR) {
sw = Iso7816.SW_6A80_WRONG_DATA;
note = "The given terminal type and password does not match the access rights";
}
}
}
//check passwords
boolean paceSuccessful = false;
ResponseData responseData;
if (sw == Iso7816.SW_9000_NO_ERROR){
responseData = AbstractPaceProtocol.isPasswordUsable(passwordObject, cardState);
if (responseData == null){
responseObjects.addTlvDataObject(new PrimitiveTlvDataObject(TAG_80, Utils.toUnsignedByteArray(SW_9000_NO_ERROR)));
} else {
//add MseSetAT SW to response data
responseObjects.addTlvDataObject(new PrimitiveTlvDataObject(TAG_80, Utils.toUnsignedByteArray(responseData.getStatusWord())));
}
}
if (sw == Iso7816.SW_9000_NO_ERROR){
if((passwordObject != null) && (providedPassword != null) && Arrays.equals(providedPassword, passwordObject.getPassword())) {
log(this, "Provided password matches expected one", DEBUG);
if(passwordObject instanceof PasswordAuthObjectWithRetryCounter) {
ResponseData pinResponse = AbstractPaceProtocol.getMutualAuthenticatePinManagementResponsePaceSuccessful(passwordObject, cardState);
sw = pinResponse.getStatusWord();
note = pinResponse.getResponse();
paceSuccessful = !Iso7816Lib.isReportingError(sw);
} else{
sw = Iso7816.SW_9000_NO_ERROR;
note = "MutualAuthenticate processed successfully";
paceSuccessful = true;
}
} else{
//PACE failed
log(this, "Provided password does NOT match expected one", DEBUG);
paceSuccessful = false;
if(passwordObject instanceof PasswordAuthObjectWithRetryCounter) {
ResponseData pinResponse = AbstractPaceProtocol.getMutualAuthenticatePinManagementResponsePaceFailed((PasswordAuthObjectWithRetryCounter) passwordObject);
sw = pinResponse.getStatusWord();
note = pinResponse.getResponse();
} else{
sw = Iso7816.SW_6300_AUTHENTICATION_FAILED;
note = "Provided password does NOT match expected one";
}
}
}
if(paceSuccessful) {
byte[] compEphermeralPublicKey = HexString.toByteArray("0102030405060708900A0B0C0D0E0F1011121314"); //arbitrary selected value
TlvDataObject primitive86 = new PrimitiveTlvDataObject(TAG_86, compEphermeralPublicKey);
responseObjects.addTlvDataObject(primitive86);
//add CARs to response data if available
if (trustPoint != null) {
if (trustPoint.getCurrentCertificate() != null
&& trustPoint.getCurrentCertificate()
.getCertificateHolderReference() instanceof PublicKeyReference) {
responseObjects
.addTlvDataObject(new PrimitiveTlvDataObject(
TAG_87, trustPoint.getCurrentCertificate()
.getCertificateHolderReference()
.getBytes()));
if (trustPoint.getPreviousCertificate() != null
&& trustPoint.getPreviousCertificate()
.getCertificateHolderReference() instanceof PublicKeyReference) {
responseObjects
.addTlvDataObject(new PrimitiveTlvDataObject(
TAG_88,
trustPoint
.getPreviousCertificate()
.getCertificateHolderReference()
.getBytes()));
}
}
}
//enable pseudo SM
pseudoSmIsActive = true;
//propagate data about successfully performed SecMechanism in SecStatus
if (sw == Iso7816.SW_9000_NO_ERROR){
Oid terminalTypeOid = usedChat != null ? usedChat.getObjectIdentifier(): null;
PaceMechanism paceMechanism = new PaceMechanism(passwordObject, compEphermeralPublicKey, terminalTypeOid);
if (usedChat != null){
HashMap<Oid, Authorization> authorizations = new HashMap<>();
authorizations.put(usedChat.getObjectIdentifier(), usedChat.getRelativeAuthorization());
AuthorizationStore authorizationStore = new AuthorizationStore(authorizations);
ConfinedAuthorizationMechanism authMechanism = new ConfinedAuthorizationMechanism(authorizationStore);
processingData.addUpdatePropagation(this, "Security status updated with authorization mechanism", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, authMechanism));
}
processingData.addUpdatePropagation(this, "Security status updated with PACE mechanism", new SecStatusMechanismUpdatePropagation(SecContext.APPLICATION, paceMechanism));
}
note = "Established PACE Bypass";
}
// build and propagate response Apdu
TlvValue responseTlvData = new TlvDataObjectContainer(responseObjects);
ResponseApdu responseApdu = new ResponseApdu(responseTlvData, sw);
processingData.updateResponseAPDU(this, note, responseApdu);
}
/**
* Handle pseudo SM APDU.
* <p/>
* After PACE was successfully initialized through
* {@link #processInitPaceBypass(ProcessingData)} pseudo SM is initiated,
* that does not provide any kind of security. This is indicated by usage of
* the otherwise unused logical Channel 3 e.g. the lowest two bits of CLA
* are set.
* <p/>
* This method removes these flagging bits and ensures that the "decoded"
* commandApdu correctly returns on
* {@link IsoSecureMessagingCommandApdu#wasSecureMessaging()}
* <p/>
* Responses are simply returned in plain. Pseudo SM is aborted when an SM
* 6987 or 6988 is returned or whenever a plain (unflagged) APDU is
* transmitted.
*/
private void processSm(ProcessingData processingData) {
CommandApdu commandApdu = processingData.getCommandApdu();
byte cla = commandApdu.getCla();
if ((cla&0x03) != 0x03) {
if (pseudoSmIsActive) {
//TODO reformulate this expression
if (!(commandApdu instanceof IsoSecureMessagingCommandApdu) || !((IsoSecureMessagingCommandApdu) commandApdu).wasSecureMessaging()){
log(this, "Plain APDU received, breaking pseudo SM");
pseudoSmIsActive = false;
//remove from protocol stack
processingData.addUpdatePropagation(this, "Pseudo SM deactivated, no need to stay on stack", new ProtocolUpdate(true));
}
}
return;
}
//ignore everything when pseudo SM is not active
if (!pseudoSmIsActive ) {
//do nothing with this APDU
return;
}
//add a dummy APDU in the chain that indicates wasSecureMessaging()
SmMarkerApdu smMarkerApdu = new SmMarkerApdu(commandApdu);
processingData.updateCommandApdu(this, "SM marker APDU added", smMarkerApdu);
//unmask the pseudo SM CLA and create new CommandApdu
byte[] apduBytes = commandApdu.toByteArray();
apduBytes[0] &= (byte) 0xFC;
processingData.updateCommandApdu(this, "Unmasked plain APDU", CommandApduFactory.createCommandApdu(apduBytes, smMarkerApdu));
}
@Override
public String getIDString() {
return "PaceBypass";
}
@Override
public void reset() {
//do NOT reset anything here (as this might be called when the protocol is still active on stack)
}
@Override
public boolean isMoveToStackRequested() {
return moveToStack;
}
}