package de.persosim.simulator.apdumatching; import de.persosim.simulator.apdu.CommandApduImpl; import de.persosim.simulator.apdu.InterindustryCommandApdu; import static org.globaltester.logging.BasicLogger.log; import de.persosim.simulator.apdu.CommandApdu; import de.persosim.simulator.apdu.IsoSecureMessagingCommandApdu; import de.persosim.simulator.exception.CommandParameterUndefinedException; import de.persosim.simulator.platform.Iso7816; import de.persosim.simulator.tlv.TlvDataObjectContainer; import de.persosim.simulator.tlv.TlvPath; import de.persosim.simulator.tlv.TlvTag; /** * This class specifies requirements that must be met by an APDU to positively match. * * @author slutters * */ public class ApduSpecification implements Iso7816, ApduSpecificationConstants { /* The id, e.g. the name of the resembled APDU */ protected String id; /* Indicates whether the resembled APDU is able to start a protocol */ protected boolean isInitialAPDU; /* ISO format as defined in ISO7816 interface */ protected byte isoFormat; /* Indicates whether the resembled APDU is expected to use chaining as * defined in ISO7816 interface (CHAINING_X)*/ protected boolean chaining; /* Indicates which chaining mode the resembled APDU is expected to use * as defined in ISO7816 interface (SM_X) */ protected byte secureMessaging; /* Indicates which channel the resembled APDU is expected to use as * defined in ISO7816 interface (CH_X) */ protected byte channel; protected byte isoCase; protected boolean isExtendedLengthLCLE; protected byte ins; protected byte p1; protected byte p2; protected TlvSpecificationContainer tags; /* * REQ_FAIL -> must not match the indicated parameter * REQ_UNDEFINED -> may or may not match the indicated parameter (parameter insignificant) * REQ_MATCH -> must match the indicated parameter */ protected byte reqIsoFormat, reqChaining, reqSecureMessaging, reqChannel, reqIsoCase, reqIsExtendedLengthLCLE, reqIns, reqP1, reqP2; /*--------------------------------------------------------------------------------*/ public ApduSpecification(String id) { this.id = id; this.isInitialAPDU = false; this.tags = new TlvSpecificationContainer(); } /*--------------------------------------------------------------------------------*/ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (chaining ? 1231 : 1237); result = prime * result + channel; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ins; result = prime * result + (isExtendedLengthLCLE ? 1231 : 1237); result = prime * result + (isInitialAPDU ? 1231 : 1237); result = prime * result + isoCase; result = prime * result + isoFormat; result = prime * result + p1; result = prime * result + p2; result = prime * result + reqChaining; result = prime * result + reqChannel; result = prime * result + reqIns; result = prime * result + reqIsExtendedLengthLCLE; result = prime * result + reqIsoCase; result = prime * result + reqIsoFormat; result = prime * result + reqP1; result = prime * result + reqP2; result = prime * result + reqSecureMessaging; result = prime * result + secureMessaging; result = prime * result + ((tags == null) ? 0 : tags.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ApduSpecification other = (ApduSpecification) obj; if (chaining != other.chaining) return false; if (channel != other.channel) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (ins != other.ins) return false; if (isExtendedLengthLCLE != other.isExtendedLengthLCLE) return false; if (isInitialAPDU != other.isInitialAPDU) return false; if (isoCase != other.isoCase) return false; if (isoFormat != other.isoFormat) return false; if (p1 != other.p1) return false; if (p2 != other.p2) return false; if (reqChaining != other.reqChaining) return false; if (reqChannel != other.reqChannel) return false; if (reqIns != other.reqIns) return false; if (reqIsExtendedLengthLCLE != other.reqIsExtendedLengthLCLE) return false; if (reqIsoCase != other.reqIsoCase) return false; if (reqIsoFormat != other.reqIsoFormat) return false; if (reqP1 != other.reqP1) return false; if (reqP2 != other.reqP2) return false; if (reqSecureMessaging != other.reqSecureMessaging) return false; if (secureMessaging != other.secureMessaging) return false; if (tags == null) { if (other.tags != null) return false; } else if (!tags.equals(other.tags)) return false; return true; } /** * This method performs a matching for a single provided parameter * @param name the name of the matching parameter * @param expected the expected value * @param received the received value * @param required whether this parameter is required * @return whether the provided parameter matches */ private boolean matchByteParameter(String name, byte expected, byte received, byte required) { if(expected == received) { if(required == REQ_MISMATCH) { log(ApduSpecification.class, name + " must not be " + String.format("%02X", received)); return false; } } else{ if(required == REQ_MATCH) { log(ApduSpecification.class, name + " expected to be " + String.format("%02X", expected)); return false; } } return true; } /** * This method performs a matching of the specification defined within this object against the provided {@link CommandApduImpl}. * The matching is positive only iff all parameters match. * Parameters match iff the received parameters are optional, equal the expected ones or do not equal parameters expected to mismatch. * @param apdu the {@link CommandApdu} to match * @return whether the specification defined within this object matches against the provided {@link CommandApduImpl} */ public boolean matchesFullApdu(CommandApdu apdu) { byte isoCaseReceived; boolean elementMatch; elementMatch = matchByteParameter("ISO format", isoFormat, apdu.getIsoFormat(), reqIsoFormat); if(!elementMatch) {return false;} if (reqChaining != REQ_OPTIONAL) { if (!(apdu instanceof InterindustryCommandApdu)) { log(ApduSpecification.class, "apdu class does not support channels"); return false; } if(this.chaining == ((InterindustryCommandApdu) apdu).isChaining()) { if(this.reqChaining == REQ_MISMATCH) { if(this.chaining) { log(ApduSpecification.class, "chaining is not supported"); return false; } else{ log(ApduSpecification.class, "chaining expected"); return false; } } } else{ if(this.reqChaining == REQ_MATCH) { if(this.chaining) { log(ApduSpecification.class, "chaining expected"); return false; } else{ log(ApduSpecification.class, "chaining is not supported"); return false; } } } } if(reqSecureMessaging != REQ_OPTIONAL) { CommandApdu curApdu = apdu; while (curApdu != null) { if ((curApdu instanceof IsoSecureMessagingCommandApdu) && (secureMessaging == ((IsoSecureMessagingCommandApdu) curApdu).getSecureMessaging())) { if(reqSecureMessaging == REQ_MATCH) { break; } else { log(ApduSpecification.class, "SM mismatch not fulfilled"); return false; } } //check predecessor curApdu = curApdu.getPredecessor(); } } if (reqChannel != REQ_OPTIONAL) { if (!(apdu instanceof InterindustryCommandApdu)) { //XXX use a marker interface to check for channel abilities to provide more generic way log(ApduSpecification.class, "apdu class does not support channels"); return false; } elementMatch = matchByteParameter("channel", channel, ((InterindustryCommandApdu) apdu).getChannel(), reqChannel); if(!elementMatch) {return false;} } elementMatch = matchByteParameter("INS byte", ins, apdu.getIns(), reqIns); if(!elementMatch) {return false;} elementMatch = matchByteParameter("P1 byte", p1, apdu.getP1(), reqP1); if(!elementMatch) {return false;} elementMatch = matchByteParameter("P2 byte", p2, apdu.getP2(), reqP2); if(!elementMatch) {return false;} isoCaseReceived = apdu.getIsoCase(); elementMatch = matchByteParameter("ISO case", isoCase, isoCaseReceived, reqIsoCase); if(!elementMatch) {return false;} if (reqIsExtendedLengthLCLE != REQ_OPTIONAL) { if (isoCaseReceived == 1) { log(ApduSpecification.class, "unable to determine extended length for iso case 1 apdu"); return false; } if(this.isExtendedLengthLCLE == apdu.isExtendedLength()) { if(this.reqIsExtendedLengthLCLE == REQ_MISMATCH) { if(this.isExtendedLengthLCLE) { log(ApduSpecification.class, "extended length L_C/L_E fields must not be used"); return false; } else{ log(ApduSpecification.class, "extended length L_C/L_E fields expected"); return false; } } } else{ if(this.reqIsExtendedLengthLCLE == REQ_MATCH) { if(this.isExtendedLengthLCLE) { log(ApduSpecification.class, "extended length L_C/L_E fields expected"); return false; } else{ log(ApduSpecification.class, "extended length L_C/L_E fields must not be used"); return false; } } } } if (!tags.isEmpty()) { TlvDataObjectContainer constructedDataField; byte[] commandDataBytes = apdu.getCommandData().toByteArray(); try { constructedDataField = new TlvDataObjectContainer(commandDataBytes, 0, commandDataBytes.length); return tags.matches(constructedDataField); } catch (IllegalArgumentException e) { log(ApduSpecification.class, "command data field does not contain TLV constructed data"); return false; } } return true; } /*--------------------------------------------------------------------------------*/ /** * @return the isInitialAPDU */ public boolean isInitialAPDU() { return isInitialAPDU; } public void setInitialApdu() { this.isInitialAPDU = true; } /** * @param isInitialAPDU the isInitialAPDU to set */ public void setInitialAPDU(boolean isInitialAPDU) { this.isInitialAPDU = isInitialAPDU; } /** * @return the id */ public String getId() { return id; } /*--------------------------------------------------------------------------------*/ /** * @return the isoFormat */ public byte getIsoFormat() { if(this.reqIsoFormat == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("ISO format undefined"); } return isoFormat; } /** * @param isoFormat the isoFormat to set */ public void setIsoFormat(byte isoFormat) { this.isoFormat = isoFormat; this.reqIsoFormat = REQ_MATCH; } /** * @return the chaining */ public boolean isChaining() { if(this.reqChaining == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("chaining undefined"); } return chaining; } /** * @param chaining the chaining to set */ public void setChaining(boolean chaining) { this.chaining = chaining; this.reqChaining = REQ_MATCH; } /** * @return the secureMessaging */ public byte getSecureMessaging() { if(this.reqSecureMessaging == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("secure messaging undefined"); } return secureMessaging; } /** * @param secureMessaging the secureMessaging to set */ public void setSecureMessaging(byte secureMessaging) { this.secureMessaging = secureMessaging; this.reqSecureMessaging = REQ_MATCH; } /** * @return the channel */ public byte getChannel() { if(this.reqChannel == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("channel undefined"); } return channel; } /** * @param channel the channel to set */ public void setChannel(byte channel) { this.channel = channel; this.reqChannel = REQ_MATCH; } /** * @return the isoCase */ public byte getIsoCase() { if(this.reqIsoCase == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("ISO case undefined"); } return isoCase; } /** * @param isoCase the isoCase to set */ public void setIsoCase(byte isoCase) { this.isoCase = isoCase; this.reqIsoCase = REQ_MATCH; } /** * @return the isExtendedLengthLCLE */ public boolean isExtendedLengthLCLE() { if(this.reqIsExtendedLengthLCLE == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("extended length L_C/L_E undefined"); } return isExtendedLengthLCLE; } /** * @param isExtendedLengthLCLE the isExtendedLengthLCLE to set */ public void setExtendedLengthLCLE(boolean isExtendedLengthLCLE) { this.isExtendedLengthLCLE = isExtendedLengthLCLE; this.reqIsExtendedLengthLCLE = REQ_MATCH; } /** * @return the ins */ public byte getIns() { if(this.reqIns == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("instruction byte undefined"); } return ins; } /** * @param ins the ins to set */ public void setIns(byte ins) { this.ins = ins; this.reqIns = REQ_MATCH; } /** * @return the p1 */ public byte getP1() { if(this.reqP1 == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("P1 undefined"); } return p1; } /** * @param p1 the p1 to set */ public void setP1(byte p1) { this.p1 = p1; this.reqP1 = REQ_MATCH; } /** * @return the p2 */ public byte getP2() { if(this.reqP2 == REQ_OPTIONAL) { CommandParameterUndefinedException.throwIt("P2 undefined"); } return p2; } /** * @param p2 the p2 to set */ public void setP2(byte p2) { this.p2 = p2; this.reqP2 = REQ_MATCH; } /*--------------------------------------------------------------------------------*/ /** * @return the tags */ public TlvSpecificationContainer getTags() { return tags; } /*--------------------------------------------------------------------------------*/ public void addTag(TlvPath path, TlvSpecification eTagSpec) { this.tags.add(path.clone(), eTagSpec); } public void addTag(TlvSpecification eTagSpec) { this.tags.add(eTagSpec); } public void addTag(TlvTag tag) { addTag(new TlvSpecification(tag)); } /*--------------------------------------------------------------------------------*/ /** * @return the reqIsoFormat */ public byte getReqIsoFormat() { return reqIsoFormat; } /** * @param reqIsoFormat the reqIsoFormat to set */ public void setReqIsoFormat(byte reqIsoFormat) { this.reqIsoFormat = reqIsoFormat; } /** * @return the reqChaining */ public byte getReqChaining() { return reqChaining; } /** * @param reqChaining the reqChaining to set */ public void setReqChaining(byte reqChaining) { this.reqChaining = reqChaining; } /** * @return the reqSecureMessaging */ public byte getReqSecureMessaging() { return reqSecureMessaging; } /** * @param reqSecureMessaging the reqSecureMessaging to set */ public void setReqSecureMessaging(byte reqSecureMessaging) { this.reqSecureMessaging = reqSecureMessaging; } /** * @return the reqChannel */ public byte getReqChannel() { return reqChannel; } /** * @param reqChannel the reqChannel to set */ public void setReqChannel(byte reqChannel) { this.reqChannel = reqChannel; } /** * @return the reqIsoCase */ public byte getReqIsoCase() { return reqIsoCase; } /** * @param reqIsoCase the reqIsoCase to set */ public void setReqIsoCase(byte reqIsoCase) { this.reqIsoCase = reqIsoCase; } /** * @return the reqIsExtendedLengthLCLE */ public byte getReqIsExtendedLengthLCLE() { return reqIsExtendedLengthLCLE; } /** * @param reqIsExtendedLengthLCLE the reqIsExtendedLengthLCLE to set */ public void setReqIsExtendedLengthLCLE(byte reqIsExtendedLengthLCLE) { this.reqIsExtendedLengthLCLE = reqIsExtendedLengthLCLE; } /** * @return the reqIns */ public byte getReqIns() { return reqIns; } /** * @param reqIns the reqIns to set */ public void setReqIns(byte reqIns) { this.reqIns = reqIns; } /** * @return the reqP1 */ public byte getReqP1() { return reqP1; } /** * @param reqP1 the reqP1 to set */ public void setReqP1(byte reqP1) { this.reqP1 = reqP1; } /** * @return the reqP2 */ public byte getReqP2() { return reqP2; } /** * @param reqP2 the reqP2 to set */ public void setReqP2(byte reqP2) { this.reqP2 = reqP2; } }