/* * GidsApplet: A Java Card implementation of the GIDS (Generic Identity * Device Specification) specification * https://msdn.microsoft.com/en-us/library/windows/hardware/dn642100%28v=vs.85%29.aspx * Copyright (C) 2016 Vincent Le Toux(vincent.letoux@mysmartlogon.com) * * It has been based on the IsoApplet * 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 com.mysmartlogon.gidsApplet; import javacard.framework.APDU; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.SystemException; import javacard.framework.Util; public class TransmitManager { // a ram buffer for public key export (no need to allocate flash !) // memory buffer size is determined by copyRecordsToRamBuf=min 512 private static final short RAM_BUF_SIZE = (short) 530; private static final short FLASH_BUF_SIZE = (short) 1220; private byte[] ram_buf = null; // internal variables to do chaining private short[] chaining_cache = null; // store special object to returns or if null, use the ram buffer private Object[] chaining_object = null; private byte[] flash_buf = null; // number of variables for the cache private static final short CHAINING_CACHE_SIZE = (short) 6; // index of the object (when sending Record[]) private static final short CHAINING_OBJECT_INDEX = (short) 0; // current offset private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_POS = (short) 1; // max size (if ram buffer) private static final short RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING = (short) 2; // previous APDU data to check consistancy between chain private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_INS = (short) 3; private static final short RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2 = (short) 4; private static final short RAM_CHAINING_CACHE_PUT_DATA_OFFSET = (short) 5; // index of the object array private static final short CHAINING_OBJECT = (short) 0; private static final short PUT_DATA_OBJECT = (short) 1; public TransmitManager() { ram_buf = JCSystem.makeTransientByteArray(RAM_BUF_SIZE, JCSystem.CLEAR_ON_DESELECT); chaining_cache = JCSystem.makeTransientShortArray(CHAINING_CACHE_SIZE, JCSystem.CLEAR_ON_DESELECT); chaining_object = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_DESELECT); } private void Clear(boolean buffer) { if (buffer) { Util.arrayFillNonAtomic(ram_buf, (short)0, RAM_BUF_SIZE, (byte)0x00); } chaining_cache[CHAINING_OBJECT_INDEX] = 0; chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0; chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] = 0; chaining_cache[RAM_CHAINING_CACHE_PUT_DATA_OFFSET] = 0; chaining_object[CHAINING_OBJECT] = null; chaining_object[PUT_DATA_OBJECT] = null; } public byte[] GetRamBuffer() { return ram_buf; } public byte[] GetFlashBuffer() { return flash_buf; } public void ClearRamBuffer() { Clear(true); } public void ClearFlashBuffer() { if (flash_buf != null) { if(JCSystem.isObjectDeletionSupported()) { flash_buf = null; JCSystem.requestObjectDeletion(); } else { Util.arrayFillNonAtomic(flash_buf, (short)0, FLASH_BUF_SIZE, (byte)0x00); } } } /** * \brief Parse the apdu's CLA byte to determine if the apdu is the first or second-last part of a chain. * * The Java Card API version 2.2.2 has a similar method (APDU.isCommandChainingCLA()), but tests have shown * that some smartcard platform's implementations are wrong (not according to the JC API specification), * specifically, but not limited to, JCOP 2.4.1 R3. * * \param apdu The apdu. * * \return true If the apdu is the [1;last[ part of a command chain, * false if there is no chain or the apdu is the last part of the chain. */ static boolean isCommandChainingCLA(APDU apdu) { byte[] buf = apdu.getBuffer(); return ((byte)(buf[0] & (byte)0x10) == (byte)0x10); } public void processChainInitialization(APDU apdu) { byte buffer[] = apdu.getBuffer(); byte ins = buffer[ISO7816.OFFSET_INS]; // Command chaining checks & initialization if(chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] != 0 || isCommandChainingCLA(apdu)) { short p1p2 = Util.getShort(buffer, ISO7816.OFFSET_P1); /* * Command chaining only for: * - PERFORM SECURITY OPERATION * - GENERATE ASYMMETRIC KEYKAIR * - PUT DATA * when not using extended APDUs. */ if( (ins != GidsApplet.INS_PERFORM_SECURITY_OPERATION && ins != GidsApplet.INS_GENERATE_ASYMMETRIC_KEYPAIR && ins != GidsApplet.INS_PUT_DATA)) { ISOException.throwIt(ErrorCode.SW_COMMAND_CHAINING_NOT_SUPPORTED); } if(chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] == 0 && chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] == 0) { /* A new chain is starting - set the current INS and P1P2. */ if(ins == 0) { ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] = ins; chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] = p1p2; } else if(chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] != ins || chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] != p1p2) { /* The current chain is not yet completed, * but an apdu not part of the chain had been received. */ ISOException.throwIt(ErrorCode.SW_COMMAND_NOT_ALLOWED_GENERAL); } else if(!isCommandChainingCLA(apdu)) { /* A chain is ending, set the current INS and P1P2 to zero to indicate that. */ chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_INS] = 0; chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_P1P2] = 0; } } // If the card expects a GET RESPONSE, no other operation should be requested. if(chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] > 0 && ins != GidsApplet.INS_GET_RESPONSE) { // clear the buffer Clear(true); } if (ins != GidsApplet.INS_PUT_DATA) { clearCachedRecord(); } } /** * \brief Receive the data sent by chaining or extended apdus and store it in ram_buf. * * This is a convienience method if large data has to be accumulated using command chaining * or extended apdus. The apdu must be in the INITIAL state, i.e. setIncomingAndReceive() * might not have been called already. * * \param apdu The apdu object in the initial state. * * \throw ISOException SW_WRONG_LENGTH */ public short doChainingOrExtAPDU(APDU apdu) throws ISOException { return doChainingOrExtAPDUWithBuffer(apdu, ram_buf, RAM_BUF_SIZE); } public short doChainingOrExtAPDUFlash(APDU apdu) throws ISOException { // allocate flash buffer only when needed - it can remain for the rest of the card life if (flash_buf == null) { try { flash_buf = new byte[FLASH_BUF_SIZE]; } catch(SystemException e) { if(e.getReason() == SystemException.NO_RESOURCE) { ISOException.throwIt(ISO7816.SW_FILE_FULL); } ISOException.throwIt(ISO7816.SW_UNKNOWN); } } return doChainingOrExtAPDUWithBuffer(apdu, flash_buf, FLASH_BUF_SIZE); } private short doChainingOrExtAPDUWithBuffer(APDU apdu, byte[] databuffer, short bufferlen) throws ISOException { short recvLen = apdu.setIncomingAndReceive(); byte[] buf = apdu.getBuffer(); // Receive data (short or extended). while (recvLen > 0) { if((short)(chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] + recvLen) > bufferlen) { ISOException.throwIt(ISO7816.SW_FILE_FULL); } Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, databuffer, chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS], recvLen); chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] += recvLen; recvLen = apdu.receiveBytes(ISO7816.OFFSET_CDATA); } if(isCommandChainingCLA(apdu)) { // We are still in the middle of a chain, otherwise there would not have been a chaining CLA. // Make sure the caller does not forget to return as the data should only be interpreted // when the chain is completed (when using this method). ISOException.throwIt(ISO7816.SW_NO_ERROR); return (short)0; } else { // Chain has ended or no chaining. // We did receive the data, everything is fine. // Reset the current position in ram_buf. recvLen = (short) (recvLen + chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]); chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0; return recvLen; } } /** * \brief Process the GET RESPONSE APDU (INS=C0). * * If there is content available in ram_buf that could not be sent in the last operation, * the host should use this APDU to get the data. The data is cached in ram_buf. * * \param apdu The GET RESPONSE apdu. * * \throw ISOException SW_CONDITIONS_NOT_SATISFIED, SW_UNKNOWN, SW_CORRECT_LENGTH. */ public void processGetResponse(APDU apdu) { sendData(apdu); } private short copyRecordsToRamBuf(short le) { short index = chaining_cache[CHAINING_OBJECT_INDEX]; Record[] records = (Record[]) (chaining_object[CHAINING_OBJECT]); short dataCopied = 0; short pos = chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]; while (records[index] != null) { byte[] data = records[index].GetData(); short dataToCopy = (short)(data.length - pos); if ((short)(dataToCopy + dataCopied) > 512) { dataToCopy = (short) (512 - dataCopied); } Util.arrayCopyNonAtomic(data, pos, ram_buf, dataCopied, dataToCopy); if ((short) (dataCopied + dataToCopy) == le) { chaining_cache[CHAINING_OBJECT_INDEX] = (short) (index + (short) 1); chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = 0; } else if (dataCopied < le && ((short) (dataCopied + dataToCopy)) > le) { chaining_cache[CHAINING_OBJECT_INDEX] = index; chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = (short) (pos + le - dataCopied); } index++; pos = 0; dataCopied += dataToCopy; if (dataCopied >= 512) { // no need to copy more data. return dataCopied; } } // if we are here, we have less than 512 bytes of data return dataCopied; } /** * \brief Send the data from ram_buf, using either extended APDUs or GET RESPONSE. * * \param apdu The APDU object, in STATE_OUTGOING state. * * \param pos The position in ram_buf at where the data begins * * \param len The length of the data to be sent. If zero, 9000 will be * returned */ private void sendData(APDU apdu) { short le; short remaininglen = 0; byte data[] = null; short pos = chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]; le = apdu.setOutgoing(); // le has not been set if(le == 0) { // we get here when called from the Shared VMWare reader byte ins = apdu.getBuffer()[ISO7816.OFFSET_INS]; if ( ins != GidsApplet.INS_GENERATE_ASYMMETRIC_KEYPAIR) { le = 256; } else { le = 0; } } if (chaining_object[CHAINING_OBJECT] == null) { data = ram_buf; remaininglen = chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING]; } else if (chaining_object[CHAINING_OBJECT] instanceof Record) { Record record = (Record) (chaining_object[CHAINING_OBJECT]); data = record.GetData(); remaininglen = (short) (((short) data.length) - pos); } else if (chaining_object[CHAINING_OBJECT] instanceof Record[]) { data = ram_buf; remaininglen = copyRecordsToRamBuf(le); pos = 0; } // We have 256 Bytes send-capacity per APDU. short sendLen = remaininglen > le ? le : remaininglen; apdu.setOutgoingLength(sendLen); apdu.sendBytesLong(data, pos, sendLen); // the position when using Record[] is maintened by copyRecordsToRamBuf if (chaining_object[CHAINING_OBJECT] == null || !(chaining_object[CHAINING_OBJECT] instanceof Record[])) { chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS]+= sendLen; } if (chaining_object[CHAINING_OBJECT] == null) { chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] -= sendLen; } remaininglen -= sendLen; if(remaininglen > 0) { short nextRespLen = remaininglen > 256 ? 256 : remaininglen; ISOException.throwIt( (short)(ISO7816.SW_BYTES_REMAINING_00 | nextRespLen) ); } else { Clear(true); return; } } public void sendRecord(APDU apdu, Record data) { Clear(true); chaining_object[CHAINING_OBJECT] = data; sendData(apdu); } public void sendRecords(APDU apdu, Record[] data) { Clear(true); chaining_object[CHAINING_OBJECT] = data; sendData(apdu); } public void sendDataFromRamBuffer(APDU apdu, short offset, short length) { Clear(false); chaining_cache[RAM_CHAINING_CACHE_OFFSET_CURRENT_POS] = offset; chaining_cache[RAM_CHAINING_CACHE_OFFSET_BYTES_REMAINING] = length; sendData(apdu); } /* functions used to cache a Record object for chained PUT DATA. * We cannot use the ram buffer because it is too small. */ public Record returnCachedRecord() { Object object = chaining_object[PUT_DATA_OBJECT]; if (object != null && object instanceof Record) { return (Record) object; } return null; } public void setCachedRecord(Record record) { chaining_object[PUT_DATA_OBJECT] = record; } public short returnCachedOffset() { return chaining_cache[RAM_CHAINING_CACHE_PUT_DATA_OFFSET]; } public void setCachedOffset(short offset) { chaining_cache[RAM_CHAINING_CACHE_PUT_DATA_OFFSET] = offset; } public void clearCachedRecord() { chaining_object[PUT_DATA_OBJECT] = null; chaining_cache[RAM_CHAINING_CACHE_PUT_DATA_OFFSET] = 0; } }