/* * IsoApplet: A Java Card PKI applet aimiing for ISO 7816 compliance. * Copyright (C) 2014 Philip Wendland (wendlandphilip@gmail.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.pwendland.javacard.pki.isoapplet; import javacard.framework.*; import net.pwendland.javacard.pki.isoapplet.UtilTLV; /** * \brief The ISO 7816 compliant IsoFileSystem class. * * It is the root of the file structure and is therefor equivalent to the ISO Master File (MF). * Normally, most of the file system oriented operations should happen through one object of this class. * * Due to the ISO 7816-4 DF and EF selection (see section 7.1) the currently selected DF and EF * are being saved internally. File-related operations are being executed upon those selected files respectively. * It is therefor possible to select a file and execute a number of operations upon this file without the need to * specify a target in each individual method call. This also saves execution time and reduces stack usage. * * \author Philip Wendland */ public class IsoFileSystem extends DedicatedFile { /* Additional ISO Status Words */ public static final short SW_COMMAND_INCOMPATIBLE_WITH_FILE_STRUCTURE = 0x6981; public static final short SW_OFFSET_OUTSIDE_EF = 0x6B00; public static final byte OFFSET_CURRENT_DF = 0; public static final byte OFFSET_CURRENT_EF = 1; private Object[] currentlySelectedFiles = null; short currentRecordNum; private boolean[] isUserAuthenticated = null; /** * \brief Instantiate a new ISO 7816 compliant IsoFileSystem. * * The IsoFileSystem class should normally only be instanciated once. It represents the file system and * is therefor equivalemt to the ISO Master File (MF). * Most of the file system related operations are performed through the returned object. * * \see IsoFileSystem. * * \param fileID The file ID of the master file. Should be 0x3F00 as specified by ISO. * * \param fileControlInformation The FCI according to ISO 7816-4 table 12. Necessary tags: 82, 83. No copy is made. */ public IsoFileSystem(short fileID, byte[] fileControlInformation) { super(fileID, fileControlInformation); this.currentRecordNum = 0; this.isUserAuthenticated = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_DESELECT); this.currentlySelectedFiles = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_DESELECT); this.currentlySelectedFiles[OFFSET_CURRENT_DF] = this; } /** * \brief Instantiate a new ISO 7816 compliant IsoFileSystem with a typical MF (FID 0x3F00, not shareable). * * \see IsoFileSystem(short fileID, byte[] fileControlInformation) */ public IsoFileSystem() { this((short) 0x3F00, new byte[] {(byte)0x6F, (byte)0x07, // FCI, Length 7. (byte)0x82, (byte)0x01, (byte)0x38, // File descriptor byte. (byte)0x83, (byte)0x02, (byte)0x3F, (byte)0x00 }); // File ID. } /** * \brief Get the currently selected DF. * * \return The currently selected DF. */ public DedicatedFile getCurrentlySelectedDF() { return ((DedicatedFile)currentlySelectedFiles[OFFSET_CURRENT_DF]); } /** * \brief Set the currently selected DF. * * \param fileID The ID of the file. * * \throw NotFoundException If the specified file was not found or was of the wrong type. */ public void setCurrentlySelectedDF(short fileID) throws NotFoundException { selectFile( findFile(fileID, SPECIFY_DF) ); return; } /** * \brief Get the currently selected Elementary File. * * \return The currently selected EF. */ public ElementaryFile getCurrentlySelectedEF() { return ((ElementaryFile)currentlySelectedFiles[OFFSET_CURRENT_EF]); } /** * \brief Set the currently selected Elementary File. * * \brief fileID The ID of the file. * * \throw NotFoundException If the specified file was not found or was of the wrong type. */ public void setCurrentlyselectedEF(short fileID) throws NotFoundException { selectFile( findFile(fileID, SPECIFY_EF) ); return; } /** * \brief Get the number of the current record for the currently selected EF. * * \return The record number. */ public short getCurrentRecordNumber() { return currentRecordNum; } /** * \brief Set the number of the current record for the currently selected EF. * * \attention The currently selected EF must be of the class ElementaryFileLinearVariable or a subclass. * * \param recordID The number of the record. Must be a legal value i.e. in range for the current EF. */ public void setCurrentRecordNumber(short recordID) { if ((getCurrentlySelectedEF() instanceof ElementaryFileLinearVariable) &&(((ElementaryFileLinearVariable)getCurrentlySelectedEF()).getCurrentRecordCount() > recordID) && (recordID >= 0)) { currentRecordNum = recordID; } } /** * \brief Set the Authentication status of the user. * * Note: Access control is only forced for operations of the IsoFileSystem class (processXXX methods). * It is the responsibility of the applet to enforce security policy for individual file operations, * even those that the IsoFileSystem saves references for, if it uses any other method to manipulate * files. */ public void setUserAuthenticated(boolean isAuthenticated) { this.isUserAuthenticated[0] = isAuthenticated; } /** * \brief Check wether the operation is valid according to the security status of the * filesystem. * * If the operation is permitted, this method just returns. If not, it throws an * ISOException with a SECURITY STATUS NOT SATISFIED status word. The processing * of the current APDU will be aborted. * * \param file The file the opertaion is executed upon. * * \param flag_operation A flag of ACL_OP_* to specify the operation. * * \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED if the operation is not * permitted. */ public void authenticateAction(File file, byte flag_operation) throws ISOException { if (file == null) { return; } byte acl = file.getACLRequirements(flag_operation); if(acl == (byte) 0x00) { // No restrictions. return; } else if(acl == (byte) 0xFF) { // Never. ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } else if(acl == (byte) 0x90 || (byte)(acl&(byte)0x9F) == (byte)0x10) { // PIN required. if(isUserAuthenticated[0]) { return; } else { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } else { ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); } } /** * \brief Search for the DF with the specified name. * * \param dfName The array containing the up to 16 byte long DedicatedFile name. * * \param nameOffset The offset at which the DF name begins in the name array. * * \param nameLength The length of the DF name. * * \throw NotFoundException If the file was not found. * * \return The requested DedicatedFile (if found). */ public DedicatedFile findDedicatedFileByName(byte[] dfName, short nameOffset, short nameLength) throws NotFoundException { if (isName(dfName, nameOffset, nameLength)) { return this; } return super.findDedicatedFileByNameRec(dfName, nameOffset, nameLength); } /** * \brief find the file with the specified file ID. * * \param fileID the ID of the file. * * \param flag A flag to specify if the currently selected EF or DF is the target (SPECIFY_EF, SPECIFY_DF, SPECIFY_ANY). * * \throw NotFoundException If the file could not be found. * * \return The File (if found). */ public File findFile(short fileID, byte flag) throws NotFoundException { if(fileID == getFileID() && flag != SPECIFY_EF) { return this; } return super.findChildrenRec(fileID, flag); } /** * \brief Set the given file as the selected. * * If the file is a DedicatedFile, only the currently selected DF is changed. * In case of an ElementaryFile the currently selected EF will be the file specified and the * currently selected DF will become its parent according to ISO 7816-4, section 7.1.1. * * \param file The file to select. Must be of DedicatedFile, IsoFileSystem or any subclass of ElementaryFile. * It should be member of the file system hierarchy (not checked). */ public void selectFile(File file) { if(file == null) { currentlySelectedFiles[OFFSET_CURRENT_DF] = this; currentlySelectedFiles[OFFSET_CURRENT_EF] = null; } else if(file instanceof DedicatedFile) { currentlySelectedFiles[OFFSET_CURRENT_DF] = file; } else if (file instanceof ElementaryFile) { currentlySelectedFiles[OFFSET_CURRENT_EF] = file; currentlySelectedFiles[OFFSET_CURRENT_DF] = ((ElementaryFile)currentlySelectedFiles[OFFSET_CURRENT_EF]).getParentDF(); this.currentRecordNum = 0; } return; } /** * \brief Add a file to the currently selected DedicatedFile. * * The currently selected DF becomes the parent of the file. * The DF's child and the EF's parent relation is being updated. * * \param file A reference of the file to save. * * \throw NotEnoughSpaceException If the maximum amount of * children would have been exceeded. */ public void addFile(File file) throws NotEnoughSpaceException { file.setParentDF(getCurrentlySelectedDF()); getCurrentlySelectedDF().addChildren(file); return; } /** * \brief "Safely" instantiate a File according to the provided File Control Information. * * Used by processCreateFile(). * * \callergraph * * \param fci The array containing the file control information (FCI) according to * ISO7816-4 table 12. Mandatory Tags: 82, 83. A copy of the FCI will be * made for the new file. * * \param offset The offset of the FCI information in the array. * * \param length The length of the FCI information. Should be consistent with the length * field if the FCI (6F) tag. * * \throw ISOException SW_SECURITY_STATUS_NOT_SATISFIED. * * \return The new file of the FCI was valid, null else. */ public File getSafeFile(byte[] fci, short offset, short length) throws ISOException, InvalidArgumentsException, NotFoundException { short fileID; byte fileDescByte; final short innerLength, innerOffset; short pos, len; /* ********************** * Check FCI structure. * ************************/ // Are we in bounds? if((short)(fci.length) <= (short)(offset+length)) { throw InvalidArgumentsException.getInstance(); } // FCI must begin with tag "6F". Or we have FCP, tag "62". if(fci[(offset)] != (byte) 0x6F && fci[(offset)] != (byte) 0x62) { throw NotFoundException.getInstance(); } // length and length-field of outer FCI tag consistency check. innerLength = UtilTLV.decodeLengthField(fci, (short)(offset+1)); if(innerLength != (short)(length-1-UtilTLV.getLengthFieldLength(innerLength))) { throw InvalidArgumentsException.getInstance(); } // Let innerOffset point to the first inner TLV entry. innerOffset = (short) (offset + 1 + UtilTLV.getLengthFieldLength(innerLength)); // Now we check for the consistency of the lower level TLV entries. if( ! UtilTLV.isTLVconsistent(fci, innerOffset, innerLength) ) { throw InvalidArgumentsException.getInstance(); } // Extract the FID from the FCI which is passed to the FileXXX contructor and saved // separately for performance reasons. pos = UtilTLV.findTag(fci, innerOffset, innerLength, (byte) 0x83); len = UtilTLV.decodeLengthField(fci, (short)(pos+1)); if (len != (short) 2) { throw InvalidArgumentsException.getInstance(); } fileID = Util.getShort(fci, (short)(pos+1+UtilTLV.getLengthFieldLength(len))); // The fileID must be unique. try { this.findFile(fileID, SPECIFY_ANY); throw InvalidArgumentsException.getInstance(); } catch(NotFoundException e) { } // Search the ACL tag (86). If not found, then we do not have any ACL and any // action may be performed. try { pos = UtilTLV.findTag(fci, innerOffset, innerLength, (byte) 0x86); len = UtilTLV.decodeLengthField(fci, (short)(pos+1)); // If we have an ACL, the length must be 8. (1 access mode byte according to ISO 7816-4 // tables 16 and 17, followed by 7 security condition bytes according to table 20.) // I.e. we require every security condition byte to be present. This eases ACL calculation. if(len != (short)8) { throw InvalidArgumentsException.getInstance(); } } catch (NotFoundException e) { // Ignore error (no ACL present). } // Check and get the File Descriptor Byte (ISO 7816-4 table 14). pos = UtilTLV.findTag(fci, innerOffset, innerLength, (byte) 0x82); len = UtilTLV.decodeLengthField(fci, (short)(pos+1)); // Ensure position found and correct length: if(len < (short)1 || len > (short)6) { throw InvalidArgumentsException.getInstance(); } fileDescByte = fci[(short)(pos+2)]; byte[] fciEEPROM = null; if((fileDescByte & 0x3F) == 0x38) { // DF // Check the permissions. authenticateAction(((DedicatedFile)currentlySelectedFiles[OFFSET_CURRENT_DF]), ACL_OP_CREATE_DF); fciEEPROM = new byte[length]; Util.arrayCopy(fci, offset, fciEEPROM, (short) 0, length); return new DedicatedFile(fileID, fciEEPROM); } else if((fileDescByte & 0x30) == 0x00) { /* EF (Working or Internal) */ // Check the permissions. authenticateAction(((DedicatedFile)currentlySelectedFiles[OFFSET_CURRENT_DF]), ACL_OP_CREATE_EF); // Get max record size and number of records. // Note: pos is still at the file descriptor byte tag. short recordSize = 256; // (assumed) default record size. byte maxRecords = 8; // (assumed) default record count. switch(fci[(short)(pos+1)]) { // switch on the length: see ISO 7816-4 Table 12, Tag 82. case 0x03: // max record size is 1 byte long. recordSize = fci[(short)(pos+4)]; break; case 0x04: // max record size is 2 bytes long. recordSize = Util.getShort(fci, (short)(pos+4)); break; case 0x05: // max record size is 2 bytes long, number of records is 1 byte long. recordSize = Util.getShort(fci, (short)(pos+4)); maxRecords = fci[(short)(pos+2)]; break; case 0x06: // max record size is 2 bytes long, number of records is 2 bytes long. recordSize = Util.getShort(fci, (short)(pos+4)); if(fci[(short)(pos+6)] == 0x00) { maxRecords = fci[(short)(pos+7)]; } else { // We only support up to 255 records per file. throw InvalidArgumentsException.getInstance(); } break; default: // No information given about max record size.. } /* Instantiate the file for the different EF types: */ switch(fileDescByte & 0x07) { case 0x00: /* No information given - We don't know what to do. */ throw InvalidArgumentsException.getInstance(); case 0x01: /* Transparent structure */ // Get the "data size" tag to determine how many bytes to allocate. short dataSize = 128; try { pos = UtilTLV.findTag(fci, (short)(offset+2), fci[(short)(offset+1)], (byte) 0x81); if(fci[(short)(pos+1)] == (byte) 0x02) { dataSize = Util.getShort(fci, (short) (pos+2)); } else if(fci[(short)(pos+1)] == (byte) 0x01) { dataSize = fci[(short)(pos+2)]; } else { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } catch (NotFoundException e) { // Ignore - use assumed default file size. } // Instantiate the EF. fciEEPROM = new byte[length]; Util.arrayCopy(fci, offset, fciEEPROM, (short) 0, length); return new ElementaryFileTransparent(fileID, fciEEPROM, dataSize); case 0x02: // Linear structure, fixed size, no further information: fall through. case 0x03: // Linear structure, fixed size, TLV structure (TLV is neither ensured nor interpreted by card.) fciEEPROM = new byte[length]; Util.arrayCopy(fci, offset, fciEEPROM, (short) 0, length); return new ElementaryFileLinearFixed(fileID, fciEEPROM, maxRecords, recordSize); case 0x04: // Linear structure, variable size, no further information: fall through. case 0x05: // Linear structure, varible size, TLV structure (TLV is neither ensured nor interpreted by card.) fciEEPROM = new byte[length]; Util.arrayCopy(fci, offset, fciEEPROM, (short) 0, length); return new ElementaryFileLinearVariable(fileID, fciEEPROM, maxRecords); case 0x06: // Cyclic structure, fixed size, no further indication: fall through. case 0x07: // Cyclic structure, fixed size, TLV structure (TLV is neither ensured nor interpreted by card.) fciEEPROM = new byte[length]; Util.arrayCopy(fci, offset, fciEEPROM, (short) 0, length); return new ElementaryFileCyclicFixed(fileID, fciEEPROM, maxRecords, recordSize); } // End EF (Working or internal). } else { // Not a supported file format. throw InvalidArgumentsException.getInstance(); } throw InvalidArgumentsException.getInstance(); } /* ************************************** * processXXX methods for ISO commands: * ****************************************/ /* ISO 7816-4 */ /** * \brief Process the SELECT (FILE) apdu. * * This method updates the currently selected EF or DF, according to the parameters in the apdu. * Every selection method according to ISO 7816-4 Table 39 is valid. * There are limitations of the P2 byte (b8...b1) at the moment, however: * - The first or only occurence is the only supported file occurence (b2b1 = 00) * - No FMD is returned. (b4b3 != 10, if b4b3 = 00 then the response only contains the FCP template.) * * \param apdu The SELECT (FILE) apdu * * \throw ISOException SW_INCORRECT_P1P2 and SW_FILE_NOT_FOUND. */ public void processSelectFile(APDU apdu) throws ISOException { byte[] buf = apdu.getBuffer(); byte p1 = buf[ISO7816.OFFSET_P1]; byte p2 = buf[ISO7816.OFFSET_P2]; short lc; short offset_cdata; short fid; File fileToSelect = null; // Only "first or only occurence" supported at the moment (ISO 7816-4 Table 40). if((p2 & 0xF3) != 0x00) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Bytes received must be Lc. lc = apdu.setIncomingAndReceive(); if(lc != apdu.getIncomingLength()) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } offset_cdata = apdu.getOffsetCdata(); // Select the file. switch(p1) { case 0x00: /* MF, DF or EF using FID */ if(lc == 0) { fileToSelect = this; } else if(lc == 2) { // we have a FID fid = Util.makeShort(buf[offset_cdata], buf[(short)(offset_cdata+1)]); try { fileToSelect = findFile(fid , SPECIFY_ANY); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } } break; case 0x01: /* Child DF unsing "DF identifier" (i.e. ID of a DF) */ fid = Util.makeShort(buf[offset_cdata], buf[(short)(offset_cdata+1)]); try { fileToSelect = getCurrentlySelectedDF().findChildrenRec(fid, SPECIFY_DF); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; case 0x02: /* EF under the current DF using "EF identifer" (i.e. FID of a EF) */ fid = Util.makeShort(buf[offset_cdata], buf[(short)(offset_cdata+1)]); try { fileToSelect = getCurrentlySelectedDF().findChildrenRec(fid, SPECIFY_EF); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; case 0x03: /* parent DF of the current DF */ if(lc != 0) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } // The MF ("this") has no parent. if(getCurrentlySelectedDF() != this) { fileToSelect = getCurrentlySelectedDF().getParentDF(); } else { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; case 0x04: /* by DF name */ try { fileToSelect = findDedicatedFileByName(buf, offset_cdata, lc); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; case 0x08: /* Path from MF */ try { fileToSelect = this.findChildrenByPath(buf, offset_cdata, lc); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; case 0x09: /* Path from current DF */ try { fileToSelect = getCurrentlySelectedDF().findChildrenByPath(buf, offset_cdata, lc); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } break; default: ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } selectFile( fileToSelect ); /* * The file is selected now. We still have to check P2 to see if we need to return any FCI/FCP/FMD information. * If we have to, we can use the apdu buffer to save the TLV encoded entries as that is what we want to send back anyway (for performance reasons). * We don't use javacardx.framework.tlv.BERTLV as smartcard support is scarce.. */ lc = 0; // We re-use lc here for the length of the response data. switch(p2 & 0xFC) { case 0x00: /* Return FCI. */ // FCP: if(fileToSelect.getFileControlInformation() != null) { Util.arrayCopy(fileToSelect.getFileControlInformation(), (short) 0, buf, (short) 0, (short) fileToSelect.getFileControlInformation().length); lc += (short) fileToSelect.getFileControlInformation().length; } // FMD: // TODO // Attention: Copy to buf at position lc, not position 0. break; case 0x04: // Return FCP. if(fileToSelect.getFileControlInformation() != null) { Util.arrayCopy(fileToSelect.getFileControlInformation(), (short) 0, buf, (short) 0, (short) fileToSelect.getFileControlInformation().length); lc += (short) fileToSelect.getFileControlInformation().length; } break; case 0x08: // Return FMD. // TODO ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); break; case 0x0C: // Return nothing. break; } if( lc > 0) { apdu.setOutgoingAndSend((short) 0, lc); } return; } /** * \brief Process the READ BINARY APDU. * * \param apdu The APDU (INS=B0). * * \throw ISOException SW_FUNC_NOT_SUPPORTED, SW_SECURITY_STATUS_NOT_SATISFIED, SW_INCORRECT_P1P2 * SW_FILE_NOT_FOUND and SW_COMMAND_INCOMPATIBLE_WITH_FILE_STRUCTURE. */ public void processReadBinary(APDU apdu) { byte[] buf = apdu.getBuffer(); byte p1 = buf[ISO7816.OFFSET_P1]; byte p2 = buf[ISO7816.OFFSET_P2]; // Check INS: We only support INS=B0 at the moment. if(buf[ISO7816.OFFSET_INS] == (byte) 0xB1) { ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED); } // Check P1 and P2. short offset = -1; // offset in data in EF ElementaryFile ef = null; if((p1 & 0xE0) == 0x80) { byte sfi = (byte)(p1 & 0x1F); offset = p2; try { ef = getCurrentlySelectedDF().findChildElementaryFileBySFI(sfi); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } } else if((p1 & 0x80) == 0x00) { // P1P2 except the most significant bit of P1 form the offset // This number can be up to 32767. Exactly what a signed short can hold! ;-) offset = (short)((short)(p1 & 0x7F) << (short)8); offset |= (short)(p2 & 0x00FF); ef = getCurrentlySelectedEF(); } else { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Check Access authenticateAction(ef, ACL_OP_READ_SEARCH); // EF to read from must be transparent. ElementaryFileTransparent efTr = null; if(ef instanceof ElementaryFileTransparent) { efTr = (ElementaryFileTransparent) ef; } else { ISOException.throwIt(SW_COMMAND_INCOMPATIBLE_WITH_FILE_STRUCTURE); } // Le: Length of expected data (i.e. max length of data to read). short le = apdu.setOutgoing(); byte[] fileData = efTr.getData(); // Offset in bounds? if( ((offset) >= fileData.length) || (offset < 0) ) { ISOException.throwIt(SW_OFFSET_OUTSIDE_EF); } /* * Adjust Le: If it is longer than the actual data, set it to the legnth of the actual data. * * The host may request all the data (up to 256 Bytes) with Le=00, but the data might be smaller. * This is a valid request; we have to send all the data (even if less than 256 Bytes). */ if((short) (le+offset) >= (short) fileData.length) { le = (short)((short)(fileData.length) - offset); } apdu.setOutgoingLength(le); apdu.sendBytesLong(fileData, offset, le); } // TODO WRITE BINARY If file lifecycles are to be implemented. /** * \Brief Process the UPDATE BINARY apdu. * * This method updates data already present in a transparent EF. The APDU specifies an * offset at which the update should start. Either the current EF (P1 most significant * bits 100) or an EF specified by a short EF identifier (SFI) under the current DF are * altered. At the moment, only INS=D6 APDUs are supported. * * \param apdu The APDU (INS=D6). * * \throw ISOException SW_FUNC_NOT_SUPPORTED, SW_SECURITY_STATUS_NOT_SATISFIED, SW_INCORRECT_P1P2 * SW_FILE_NOT_FOUND and SW_COMMAND_INCOMPATIBLE_WITH_FILE_STRUCTURE. */ public void processUpdateBinary(APDU apdu) { byte[] buf = apdu.getBuffer(); byte p1 = buf[ISO7816.OFFSET_P1]; byte p2 = buf[ISO7816.OFFSET_P2]; short lc; short recvLen; // Check INS: We only support INS=D6 at the moment. if(buf[ISO7816.OFFSET_INS] == (byte) 0xD7) { ISOException.throwIt(ISO7816.SW_FUNC_NOT_SUPPORTED); } // Bytes received must be Lc. recvLen = apdu.setIncomingAndReceive(); lc = apdu.getIncomingLength(); // Check P1 and P2. short offsetFile = -1; // offset in data in EF ElementaryFile ef = null; if((p1 & 0xE0) == 0x80) { byte sfi = (byte)(p1 & 0x1F); offsetFile = p2; try { ef = getCurrentlySelectedDF().findChildElementaryFileBySFI(sfi); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } } else if((p1 & 0x80) == 0x00) { // P1P2 except the most significant bit of P1 form the offset // This number can be up to 32767. Exactly what a signed short can hold! ;-) offsetFile = (short)((short)(p1 & 0x7F) << (short)8); offsetFile |= (short)(p2 & 0x00FF); ef = getCurrentlySelectedEF(); } else { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Check permissions. authenticateAction(ef, ACL_OP_UPDATE_ERASE); // EF to update must be transparent. ElementaryFileTransparent efTr = null; if(ef instanceof ElementaryFileTransparent) { efTr = (ElementaryFileTransparent) ef; } else { ISOException.throwIt(SW_COMMAND_INCOMPATIBLE_WITH_FILE_STRUCTURE); } // Update file. if(((short) efTr.getData().length >= (short) (offsetFile+lc)) // Check for data array overflow/out-of-bounds. && ((short) ((short)32767-offsetFile) >= lc)) { // Check for possible short overflow. short offsetCdata = apdu.getOffsetCdata(); while(recvLen > 0) { Util.arrayCopy(buf, offsetCdata, efTr.getData(), offsetFile, recvLen); offsetFile += recvLen; recvLen = apdu.receiveBytes(offsetCdata); } selectFile(ef); } else { ISOException.throwIt(SW_OFFSET_OUTSIDE_EF); } } // TODO SEARCH BINARY A0 A1 // TODO ERASE BINARY 0E 0F // TODO If record files are to be used: // - READ RECORD B2 B3 // - WRITE RECORD D2 // - UPDATE RECORD DC DD // - APPEND RECORD E2 // - SEARCH RECORD A2 // - ERASE RECORD 0C /* ISO 7816-9 */ /** * \brief Process the DELETE FILE apdu. * * \attention Only deletion by FID is supported. Lc must be 2, the DATA field * must contain the file ID. P1P2 must be 0000. * * \todo Add support for other file identification methods as in SELECT. * * \param apdu The DELETE FILE apdu. * * \throw ISOException SW_INCORRECT_P1P2, SW_WRONG_LENGTH, SW_FILE_NOT_FOUND and * SW_SECURITY_STATUS_NOT_SATISFIED. */ public void processDeleteFile(APDU apdu) throws ISOException { byte[] buf = apdu.getBuffer(); byte p1 = buf[ISO7816.OFFSET_P1]; byte p2 = buf[ISO7816.OFFSET_P2]; short lc; short offset_cdata; short fileID; File fileToDelete = null; // Only P1P2 = 0000 is currently supported. // (File identifier must be encoded in the command data field.) if( p1 != 0x00 || p2 != 0x00 ) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Bytes received must be Lc. lc = apdu.setIncomingAndReceive(); if(lc != apdu.getIncomingLength()) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } offset_cdata = apdu.getOffsetCdata(); // One FID in DATA. if(lc != 2) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } // Find the File. fileID = Util.getShort(buf, offset_cdata); try { fileToDelete = findFile(fileID, SPECIFY_ANY); } catch (NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } // Don't delete the MF. if(fileToDelete == this) { ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } // Permissions. authenticateAction(fileToDelete, ACL_OP_DELETE_SELF); // Update current DF before deletion. currentlySelectedFiles[OFFSET_CURRENT_DF] = (fileToDelete.getParentDF()); currentlySelectedFiles[OFFSET_CURRENT_EF] = null; // Remove from tree. Garbage collector has already been called by deleteChildren(). try { getCurrentlySelectedDF().deleteChildren(fileID); } catch(NotFoundException e) { ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } } /** * \brief Process the CREATE FILE apdu. * * This method creates a file, adds it to the filesystem structure and selects it. * Configuration options are taken from the DATA field of the APDU. (I.e. P1 and P2 must be 00.) * The data field of the APDU must be 2-level nested TLV encoded. The upper level is the FCI (6F) or FCP (62) tag. * The nested information will be added to the file as FCI. Also, the following information is being taken in * order to allocate the right ressources: * - The file ID (tag 83) * - The file description byte (tag 82) to determine the type, also following information to determine record * sizes and amounts in case of non-transparent EFs. * - In the case of a transparent EF, the data size (excluding structural information) (tag 80) in order to * allocate enough space. * * \param apdu The SELECT (FILE) apdu * * \throw ISOException SW_INCORRECT_P1P2, SW_DATA_INVALID, SW_FILE_FULL and SW_SECURITY_STATUS_NOT_SATISFIED. */ public void processCreateFile(APDU apdu) throws ISOException { byte[] buf = apdu.getBuffer(); byte p1 = buf[ISO7816.OFFSET_P1]; byte p2 = buf[ISO7816.OFFSET_P2]; short lc; short offset_cdata; // Only P1P2 = 0000 supported. // (File identifier and parameters must be encoded in the command data field.) if( p1 != 0x00 || p2 != 0x00 ) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Bytes received must be Lc. lc = apdu.setIncomingAndReceive(); if(lc != apdu.getIncomingLength()) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } offset_cdata = apdu.getOffsetCdata(); try { // Add the file to the filesystem and select it. File fileToAdd = getSafeFile(buf, offset_cdata, lc); // getSafeFile performs permission checks. addFile(fileToAdd); selectFile(fileToAdd); } catch (NotFoundException e) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } catch (InvalidArgumentsException e) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } catch(NotEnoughSpaceException e) { ISOException.throwIt(ISO7816.SW_FILE_FULL); } return; } // TODO If file lifecycles are to be implemented: // - DEACTIVATE FILE 04 // - ACTIVATE FILE 44 // - TERMINATE DF E6 // - TERMINATE EF E8 }