package de.persosim.simulator.protocols.file; import java.io.FileNotFoundException; import java.util.Arrays; import org.globaltester.simulator.SimulatorConfiguration; import de.persosim.simulator.apdu.CommandApdu; import de.persosim.simulator.apdu.ResponseApdu; import de.persosim.simulator.cardobjects.CardFile; import de.persosim.simulator.cardobjects.CardObject; import de.persosim.simulator.cardobjects.CardObjectIdentifier; import de.persosim.simulator.cardobjects.DedicatedFile; import de.persosim.simulator.cardobjects.DedicatedFileIdentifier; import de.persosim.simulator.cardobjects.ElementaryFile; import de.persosim.simulator.cardobjects.FileIdentifier; import de.persosim.simulator.cardobjects.NullCardObject; import de.persosim.simulator.cardobjects.ShortFileIdentifier; import de.persosim.simulator.exception.AccessDeniedException; import de.persosim.simulator.exception.FileIdentifierIncorrectValueException; import de.persosim.simulator.exception.FileToShortException; import de.persosim.simulator.exception.ProcessingException; import de.persosim.simulator.exception.TagNotFoundException; import de.persosim.simulator.platform.CardStateAccessor; import de.persosim.simulator.platform.Iso7816; import de.persosim.simulator.protocols.AbstractProtocolStateMachine; import de.persosim.simulator.protocols.ProtocolUpdate; import de.persosim.simulator.secstatus.SecStatus.SecContext; import de.persosim.simulator.secstatus.SecStatusMechanismUpdatePropagation; import de.persosim.simulator.tlv.PrimitiveTlvDataObject; import de.persosim.simulator.tlv.TlvDataObject; import de.persosim.simulator.tlv.TlvDataObjectContainer; import de.persosim.simulator.tlv.TlvTag; import de.persosim.simulator.tlv.TlvValue; import de.persosim.simulator.tlv.TlvValuePlain; import de.persosim.simulator.utils.Utils; /** * Back end code for the file management state machine. This class implements * ISO7816-compliant file management. * * @author mboonk * */ public abstract class AbstractFileProtocol extends AbstractProtocolStateMachine { static final byte P1_MASK_EF_IN_P1_P2 = (byte) 0b10000000; static final byte INS_MASK_ODDINS = (byte) 0x01; static final byte P1_MASK_SHORT_FILE_IDENTIFIER = (byte) 0b10000000; static final byte ODDINS_RESPONSE_TAG = 0x53; static final byte ODDINS_COMMAND_TAG = 0x54; static final byte ODDINS_COMMAND_DDO_TAG_73 = 0x73; static final byte ODDINS_COMMAND_DDO_TAG_53 = 0x53; static final short P1P2_MASK_SFI = 0b0000000000011111; static final byte P1_MASK_SFI = 0b00011111; public AbstractFileProtocol() { super("FM"); } protected void processCommandSelectFile() { CommandApdu cmdApdu = processingData.getCommandApdu(); byte p1 = cmdApdu.getP1(); byte p2 = cmdApdu.getP2(); CardFile file = null; try { switch (p1) { case P1_SELECT_FILE_MF_DF_EF: if ((p2 & P2_SELECT_OCCURRENCE_MASK) == P2_SELECT_OCCURRENCE_FIRST) { if ((cmdApdu.getCommandData().isEmpty()) || ((cmdApdu.getNc() == 2 && Arrays.equals(cmdApdu.getCommandData().toByteArray(), new byte[] { 0x3F, 0x00 })))) { // special file identifier for the master file (absent or 3f00) file = cardState.getMasterFile(); } else { file = getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new FileIdentifier( Utils.getShortFromUnsignedByteArray(cmdApdu.getCommandData().toByteArray()))); } } else { // IMPL implement handling of other file occurrence values // (ISO7816 p. 38) ResponseApdu resp = new ResponseApdu(SW_6A81_FUNC_NOT_SUPPORTED); this.processingData.updateResponseAPDU(this, "file occurence selector not supported", resp); } break; case P1_SELECT_FILE_EF_UNDER_CURRENT_DF: file = getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new FileIdentifier(Utils .getShortFromUnsignedByteArray(cmdApdu.getCommandData().toByteArray()))); break; case P1_SELECT_FILE_DF_BY_NAME: file = getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new DedicatedFileIdentifier(cmdApdu.getCommandData().toByteArray())); // IMPL support multiple calls selecting files successively (ISO7816-4 // 7.1.1) break; } if (file != null){ selectFile(file); TlvDataObjectContainer fco = getFileControlInformation(file, p2); ResponseApdu resp = new ResponseApdu(fco, SW_9000_NO_ERROR); this.processingData.updateResponseAPDU(this, "file selected successfully", resp); } } catch (FileNotFoundException e) { ResponseApdu resp = new ResponseApdu(SW_6A82_FILE_NOT_FOUND); this.processingData.updateResponseAPDU(this, "file not selected (not found)", resp); } catch (NullPointerException e) { ResponseApdu resp = new ResponseApdu(SW_6700_WRONG_LENGTH); this.processingData.updateResponseAPDU(this, "file identifier required in command datafield", resp); } processingData.addUpdatePropagation(this, "FileManagement protocol is not supposed to stay on the stack", new ProtocolUpdate(true)); } /** * @param file {@link CardFile} to get the FCI from * @param p2 the P2 byte of the {@link CommandApdu} * @return the file control information depending on the selection done in the P2 byte */ private TlvDataObjectContainer getFileControlInformation(CardFile file, byte p2) { switch (p2 & P2_SELECT_FCI_MASK){ case P2_SELECT_FCI_TEMPLATE: TlvDataObjectContainer result = new TlvDataObjectContainer(); result.addTlvDataObject(file.getFileControlParameterDataObject()); result.addTlvDataObject(file.getFileManagementDataObject()); return result; case P2_SELECT_FCP_TEMPLATE: return new TlvDataObjectContainer(file.getFileControlParameterDataObject()); case P2_SELECT_FMD_TEMPLATE: return new TlvDataObjectContainer(file.getFileManagementDataObject()); case P2_SELECT_NO_OR_PROPRIETARY: return new TlvDataObjectContainer(); } return null; } protected void processCommandEraseBinary(){ CardFile file; try { file = (CardFile) getFile(processingData.getCommandApdu(), cardState, false); } catch (FileNotFoundException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6A82_FILE_NOT_FOUND); this.processingData.updateResponseAPDU(this, "binary file not found for selection", resp); return; } ElementaryFile ef; if (!(file instanceof ElementaryFile)){ throw new ProcessingException(Iso7816.SW_6986_COMMAND_NOT_ALLOWED_NO_EF, "The used file is not an EF and can note be erased."); } else { ef = (ElementaryFile) file; } int startingOffset = getOffset(processingData.getCommandApdu()); TlvValue apduData = processingData.getCommandApdu().getCommandData(); try { if (apduData.getLength() > 0) { int endingOffset = Utils.getIntFromUnsignedByteArray(apduData.toByteArray()); ef.erase(startingOffset, endingOffset); } else { ef.erase(startingOffset); } ResponseApdu resp = new ResponseApdu( Iso7816.SW_9000_NO_ERROR); this.processingData.updateResponseAPDU(this, "binary file updated successfully", resp); processingData.addUpdatePropagation(this, "FileManagement protocol is not supposed to stay on the stack", new ProtocolUpdate(true)); } catch (AccessDeniedException e) { throw new ProcessingException(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED, "The used file can not be erased due to access conditions."); } } protected void processCommandEraseBinaryOdd(){ CardFile file; try { file = (CardFile) getFile(processingData.getCommandApdu(), cardState, true); } catch (FileNotFoundException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6A82_FILE_NOT_FOUND); this.processingData.updateResponseAPDU(this, "binary file not found for erasing", resp); return; } ElementaryFile ef; if (!(file instanceof ElementaryFile)){ throw new ProcessingException(Iso7816.SW_6986_COMMAND_NOT_ALLOWED_NO_EF, "The used file is not an EF and can not be erased."); } else { ef = (ElementaryFile) file; } try { int startingOffset = -1; int endingOffset = -1 ; try { startingOffset = Utils.getIntFromUnsignedByteArray(getDDO(processingData.getCommandApdu(), 0).getValueField()); endingOffset = Utils .getIntFromUnsignedByteArray(getDDO(processingData.getCommandApdu(), 1).getValueField()); } catch (TagNotFoundException e) { //ignore, will be handled in the following conditional } if (startingOffset < 0) { ef.erase(); } else if (endingOffset < 0) { ef.erase(startingOffset); } else { ef.erase(startingOffset, endingOffset); } ResponseApdu resp = new ResponseApdu( Iso7816.SW_9000_NO_ERROR); this.processingData.updateResponseAPDU(this, "binary file erased successfully", resp); processingData.addUpdatePropagation(this, "FileManagement protocol is not supposed to stay on the stack", new ProtocolUpdate(true)); } catch (AccessDeniedException e) { throw new ProcessingException(Iso7816.SW_6982_SECURITY_STATUS_NOT_SATISFIED, "The used file can not be erased due to access conditions."); } catch (IllegalArgumentException e){ throw new ProcessingException(Iso7816.SW_6984_REFERENCE_DATA_NOT_USABLE, "The given offsets are invalid."); } } protected void processCommandUpdateBinary() { boolean isOddInstruction = ((processingData.getCommandApdu().getIns() & INS_MASK_ODDINS) == INS_MASK_ODDINS); CardFile file; try { file = (CardFile) getFile(processingData.getCommandApdu(), cardState, isOddInstruction); } catch (FileNotFoundException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6A82_FILE_NOT_FOUND); this.processingData.updateResponseAPDU(this, "binary file not found for selection", resp); return; } int updateOffset = getOffset(processingData.getCommandApdu()); byte[] updateData = null; try { if (isOddInstruction) { updateData = getDDO(processingData.getCommandApdu(), 1) .getValueField(); } else { updateData = processingData.getCommandApdu() .getCommandData().toByteArray(); } if (file instanceof ElementaryFile) { try { ((ElementaryFile) file).update(updateOffset, updateData); selectFile((CardFile) file); ResponseApdu resp = new ResponseApdu( Iso7816.SW_9000_NO_ERROR); this.processingData.updateResponseAPDU(this, "binary file updated successfully", resp); } catch (AccessDeniedException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED); this.processingData.updateResponseAPDU(this, e.getMessage(), resp); } } else { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6986_COMMAND_NOT_ALLOWED_NO_EF); this.processingData.updateResponseAPDU(this, "no elementary file", resp); } } catch (TagNotFoundException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6A88_REFERENCE_DATA_NOT_FOUND); this.processingData.updateResponseAPDU(this, e.getMessage(), resp); } processingData.addUpdatePropagation(this, "FileManagement protocol is not supposed to stay on the stack", new ProtocolUpdate(true)); } /** * @param apdu file management {@link CommandApdu} * @return the file offset encoded in the given apdu */ private int getOffset(CommandApdu apdu){ boolean isOddInstruction = ((apdu.getIns() & INS_MASK_ODDINS) == INS_MASK_ODDINS); if (isOddInstruction){ return getOffset(apdu.getCommandDataObjectContainer()); } else { return getOffset(apdu.getP1(), apdu.getP2()); } } /** * Finds a discretionary data object at the given position. * * @param apdu * @param ddoNumber the position of the searched for DDO * @return the discretionary data object found in the given apdu at * @throws TagNotFoundException */ private TlvDataObject getDDO(CommandApdu apdu, int ddoNumber) throws TagNotFoundException{ TlvDataObjectContainer ddoEncapsulation = apdu.getCommandDataObjectContainer(); if (ddoEncapsulation.getNoOfElements() <= ddoNumber) throw new TagNotFoundException("DDO encapsulation object does not contain enough DDOs."); TlvDataObject candidate = ddoEncapsulation.getTlvObjects().get(ddoNumber); if (candidate.getTlvTag().equals(new TlvTag(ODDINS_COMMAND_TAG)) || candidate.getTlvTag().equals(new TlvTag(ODDINS_COMMAND_DDO_TAG_73)) || candidate.getTlvTag().equals(new TlvTag(ODDINS_COMMAND_DDO_TAG_53))){ return candidate; } throw new TagNotFoundException("DDO at index " + ddoNumber + " does not have tag " + ODDINS_COMMAND_TAG); } /** * This method is used if the file offset is encoded in the P1P2 bytes. * @param p1 P1 byte * @param p2 P2 byte * @return the value to be used as file offset */ private int getOffset(byte p1, byte p2) { boolean isShortFileIdentifier = (p1 & P1_MASK_EF_IN_P1_P2) == P1_MASK_EF_IN_P1_P2; if (isShortFileIdentifier) { return p2; } else { return Utils.concatenate(p1, p2); } } /** * This method is used if the file offset is encoded in a tlv object. * @param tlv {@link TlvDataObjectContainer} that contains the offset encoding * @return the value to be used as file offset */ private int getOffset(TlvDataObjectContainer tlv) { TlvDataObject offset = tlv.getTlvDataObject(new TlvTag(ODDINS_COMMAND_TAG)); return Utils.getIntFromUnsignedByteArray(offset.getValueField()); } /** * Access method for files using an odd instruction byte * @param apdu * @param cardState * @return the {@link CardObject} fitting the given apdu or null if no match is found * @throws FileNotFoundException */ private static CardObject getFileOddInstruction(CommandApdu apdu, CardStateAccessor cardState) throws FileNotFoundException{ if ((apdu.getP1P2() | P1P2_MASK_SFI) == 0b11111 && apdu.getP1P2() != 0 && apdu.getP1P2() != 0b11111){ //short file identifier in the last 5 bits of P1P2 return getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new ShortFileIdentifier(apdu.getP1P2())); } else if (apdu.getP1P2() == 0x0000){ //select the current file return CurrentFileHandler.getCurrentFile(cardState); } else { //P1P2 encodes a card file identifier return getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new FileIdentifier(apdu.getP1P2())); } } /** * Search for a child {@link CardObject} in the object * tree, starting in the given {@link DedicatedFile}. * </p> * Search pattern (according to ISO7816-4 7.1.1): * <ol> * <li> * immediate children of currently selected DF * </li> * <li> * the parent DF of the currently selected DF * </li> * <li> * immediate children of the parent DF * </li> * * @param identifier * to match the {@link CardObject}s with * @param currentDf {@link CardObject} to start the search with * @return a child that fits the given identifier or {@link NullCardObject} * if no fitting child was found */ public static CardFile getFileForSelection(DedicatedFile currentDf, CardObjectIdentifier identifier) throws FileNotFoundException{ //check the immediate children of the current DF for (CardObject curChild : currentDf.getChildren()){ if (identifier.matches(curChild) && curChild instanceof CardFile){ return (CardFile) curChild; } } //check the parentDF if (currentDf.getParent() instanceof DedicatedFile) { DedicatedFile parentDf = (DedicatedFile) currentDf.getParent(); if (identifier.matches(parentDf)){ return parentDf; } //check for parent DF immediate children for (CardObject curChild : parentDf.getChildren()){ if (identifier.matches(curChild) && curChild instanceof CardFile){ return (CardFile) curChild; } } } // No fitting child found throw new FileNotFoundException(); } /** * Access method for files using an even instruction byte * @param apdu * @param cardState * @return the {@link CardObject} fitting the given apdu or null if no match is found * @throws FileNotFoundException */ private static CardObject getFileEvenInstruction(CommandApdu apdu, CardStateAccessor cardState) throws FileNotFoundException{ if ((apdu.getP1() & P1_MASK_SHORT_FILE_IDENTIFIER) == P1_MASK_SHORT_FILE_IDENTIFIER){ int shortFileIdentifier = apdu.getP1() & P1_MASK_SFI; if (MINIMUM_SHORT_FILE_IDENTIFIER > shortFileIdentifier || MAXIMUM_SHORT_FILE_IDENTIFIER < shortFileIdentifier) { throw new FileIdentifierIncorrectValueException(); } return getFileForSelection(CurrentFileHandler.getCurrentDedicatedFile(cardState), new ShortFileIdentifier(shortFileIdentifier)); } return CurrentFileHandler.getCurrentFile(cardState); } /** * Access method for files * @param apdu * @param cardState * @return the {@link CardObject} fitting the given apdu or null if no match is found * @throws FileNotFoundException */ protected static CardObject getFile(CommandApdu apdu, CardStateAccessor cardState, boolean isOddInstruction) throws FileNotFoundException { if (isOddInstruction){ return getFileOddInstruction(apdu, cardState); } else { return getFileEvenInstruction(apdu, cardState); } } /** * Utility method to read a part of a file. * * @param offset * the offset in the file contents * @param ne * the NE fields value * @param rawFileContents * the file contents * @return the file contents starting with the offset and containing up to * NE value bytes of the file * @throws FileToShortException */ private static byte [] getFileContents(int offset, int ne, byte [] rawFileContents) throws FileToShortException{ int bytesToBeRead = Math.min(ne, rawFileContents.length - offset); if (bytesToBeRead < 0) { throw new FileToShortException(); } return Arrays.copyOfRange(rawFileContents, offset, offset + bytesToBeRead); } protected void processCommandReadBinary() { byte ins = processingData.getCommandApdu().getIns(); int ne = processingData.getCommandApdu().getNe(); int maxSize = SimulatorConfiguration.getMaxPayloadSize(); if (ne > maxSize){ ne = maxSize; } boolean isOddInstruction = ((ins & INS_MASK_ODDINS) == INS_MASK_ODDINS); boolean zeroEncoded = processingData.getCommandApdu() .isNeZeroEncoded(); int offset = getOffset(processingData.getCommandApdu()); CardObject file = null; try { file = getFile(processingData.getCommandApdu(), cardState, isOddInstruction); } catch (FileNotFoundException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6A82_FILE_NOT_FOUND); this.processingData.updateResponseAPDU(this, "binary file not found for selection", resp); } if (file instanceof ElementaryFile) { ElementaryFile binaryFile = (ElementaryFile) file; try { if (offset < binaryFile.getContent().length) { byte [] data = getFileContents(offset, ne, binaryFile.getContent()); boolean shortRead = !zeroEncoded && data.length < ne; TlvValue toSend = null; if (isOddInstruction) { toSend = new TlvDataObjectContainer( new PrimitiveTlvDataObject(new TlvTag( ODDINS_RESPONSE_TAG), data)); } else { toSend = new TlvValuePlain(data); } selectFile((CardFile)file); ResponseApdu resp = new ResponseApdu(toSend, shortRead ? Iso7816.SW_6282_END_OF_FILE_REACHED_BEFORE_READING_NE_BYTES : Iso7816.SW_9000_NO_ERROR); this.processingData.updateResponseAPDU(this, "binary file read successfully", resp); } else { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6B00_WRONG_P1P2); this.processingData.updateResponseAPDU(this, "offset behind end of file", resp); } } catch (FileToShortException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6282_END_OF_FILE_REACHED_BEFORE_READING_NE_BYTES); this.processingData.updateResponseAPDU(this, "file too short", resp); } catch (AccessDeniedException e) { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6985_CONDITIONS_OF_USE_NOT_SATISFIED); this.processingData.updateResponseAPDU(this, "binary file read access denied", resp); } } else { ResponseApdu resp = new ResponseApdu( Iso7816.SW_6986_COMMAND_NOT_ALLOWED_NO_EF); this.processingData.updateResponseAPDU(this, "no elemental file", resp); } processingData.addUpdatePropagation(this, "FileManagement protocol is not supposed to stay on the stack", new ProtocolUpdate(true)); } private void selectFile(CardFile file) { this.processingData.addUpdatePropagation(this, "select file", new SecStatusMechanismUpdatePropagation(SecContext.GLOBAL, new CurrentFileSecMechanism(file))); } }