//******************************************************************/ //* PGMID. GENERIC SMARTCARD LOGGER. */ //* AUTHOR. BERND R. FIX >Y< */ //* DATE WRITTEN. 06/08/10. */ //* COPYRIGHT. (C) BY BERND R. FIX. ALL RIGHTS RESERVED. */ //* LICENSED MATERIAL - PROGRAM PROPERTY OF THE */ //* AUTHOR. REFER TO COPYRIGHT INSTRUCTIONS. */ //* REMARKS. */ //******************************************************************/ package y67.smartcard.cardlets.logger; /////////////////////////////////////////////////////////////////////////////// //import external declarations. import javacard.framework.*; /////////////////////////////////////////////////////////////////////////////// /** * Logger cardlet: This logger allows to log communication between a terminal * application and a smartcard. The ATR of the "original card" (set externally * with "ChangeJavaATR" during deployment of the logger card) and the * class code of the "original" smartcard application (to prevent clashes with * the logger class code) are mostly known prior to logging.<p> * * Timing behaviour of the "original" smartcard is not reproduced an can * eventually lead to incorrect behaviour of the logger.<p> * * The commands are:<p> * <table> * <tr> * <td>CLA</td><td>INS</td><td>ADDR</td><td>LEN<</td><td>DATA</td> * <td>Remarks</td> * </tr> * <tr> * <td>BF</td><td>B0</td><td>addr</td><td>len</td><td> </td> * <td>LOGGER: Read data</td> * </tr> * <tr> * <td>BF</td><td>D6</td><td>addr</td><td>len</td><td>data</td> * <td>LOGGER: write data</td> * </tr> * <tr> * <td>*</td><td>*</td><td>*</td><td>*</td><td>*</td> * <td>Simulate and log communication</td> * </tr> * </table><p> * * The request/response list is limited to 255 pairs; so the overall number * of command APDUs in a single session should not exceed this limit.<p> * * @author Bernd R. Fix >Y< * @version 1.0 */ public class Logger extends Applet { //========================================================================= // Constants //========================================================================= static final byte SC_CLA = (byte) 0xF0; // smartcard OS class byte static final byte LOG_CLA = (byte) 0xBF; // logger class code //------------------------------------------------------------------------- /** * codes of INS bytes in the APDU (logger commands)<p> */ static final byte INS_READ = (byte) 0xB0; // read buffer static final byte INS_WRITE = (byte) 0xD6; // write buffer //------------------------------------------------------------------------- /** * Length of data buffers used to track communication.<p> */ static final short LENGTH_DATA = 2048; //========================================================================= // Data buffers for request, response and the last received unknown APDU //========================================================================= /** * byte array representing the available request data buffer * that is organized according the following scheme:<p> * The buffer represents a plain list of elements. Each element starts * with an index byte; the index of the last element is 0 (list * termination).<p> * * The index is follwoed by two state bytes. This allows stateful lookup * of requests and to set a new state after the request is processed. The * first byte is the state we must be in for successful lookup; a state * of "0" means "any state". The second state byte is the state we switch * to after request processing; a next value of "FF" means "no state change".<p> * * Following the state bytes is a byte length field that gives the number * of data bytes in the follwoing APDU.The rest of each element is a standard * APDU. On receiving an APDU, the applet looks up the APDU in this buffer; if * a match is found the index of the found entry is used to lookup the correct * response and to send it out.<p> * * <pre> * reqData := { * index BYTE, [0] * state BYTE, [1] * next BYTE, [2] * length BYTE, [3] * data APDU [4..n] * } * </pre> */ byte[] reqData; //------------------------------------------------------------------------- /** * Length of additional data send along with an request APDU * based on INS code.<p> */ byte[] lcData; //------------------------------------------------------------------------- /** * byte array representing the available response data buffer * that is organized according the following scheme:<p> * The buffer represents a plain list of elements. Each element starts * with an index byte; the index of the last element is 0 (list * termination).<p> * * The index byte is followed by a type byte specifying the * meaning of the next word: type 1 means Status word, * type 2 means length of following binary data.<p> * * A status word of 0000 is interpreted as "throw exception"<p> * * <pre> * respData := { * index BYTE, [0] * type BYTE, [1] * status/len WORD, [2] * data BYTE[] [4..n] * } * </pre> */ byte[] respData; //------------------------------------------------------------------------- /** * Additional ATR bytes (applet identifier). If not set (= 0), * the default value SW_NO_ERROR is returned.<p> */ static short atrAppId = 0; //------------------------------------------------------------------------- /** * APDU buffer reference.<p> */ byte[] buffer; //------------------------------------------------------------------------- /* * Transient attributes (between method calls) */ byte state; // state we are in (reset at power-up/select) short pos; // index into list short count; // counter (size) short n; // loop index short sw; // status word (usually ISO 7816 status) byte[] target; // target buffer //========================================================================= /** * Only this class's install method should create the applet object.<p> */ private Logger () { // allocate a new byte arrays as data storage. reqData = new byte[LENGTH_DATA]; reqData[0] = 0; // known requests respData = new byte[LENGTH_DATA]; respData[0] = 0; // known responses lcData = new byte[258]; // LC table // register the applet. register(); } //========================================================================= /** * Hybrid cardlet: Applet with a 'main()' method.<p> * This start-up code is responsible for sending out * additional ATR bytes. If no special applet identifier * is defined, the value SW_NO_ERROR is returned.<p> */ public static void main (String args[]) { // send additional ATR bytes (applet identifier). ISOException.throwIt (atrAppId != 0 ? atrAppId : ISO7816.SW_NO_ERROR); } //========================================================================= /** * Processes an incoming APDU.<p> * @param apdu APDU - the incoming APDU * @exception ISOException - with the response bytes per ISO 7816-4 * @see APDU */ public void process (APDU apdu) { // get access to the APDU data. buffer = apdu.getBuffer(); byte cla = buffer[ISO7816.OFFSET_CLA]; byte ins = buffer[ISO7816.OFFSET_INS]; // check if this is a commmand of the card operating system if (cla == SC_CLA) { if (ins == ISO7816.INS_SELECT) // behave gracefully. ISOException.throwIt (ISO7816.SW_NO_ERROR); return; } // decide on class code what commands are handled. switch (cla) { //----------------------------------------------------------------- // Logging applet commands. //----------------------------------------------------------------- case LOG_CLA: switch (ins) { // read request/response list. case INS_READ: readData (apdu); return; // write request/response list. case INS_WRITE: apdu.setIncomingAndReceive(); writeData (buffer); return; } break; //----------------------------------------------------------------- // "Original" applet behaviour simulation. //----------------------------------------------------------------- default: // lookup expected length of additional data count = (short)(lcData [buffer[ISO7816.OFFSET_INS] & 0xFF] &0xFF); // special length (=255)? if (count == 255) // yes: get length from LC field count = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF); if (count > 0) // receive any additional data apdu.setIncomingAndReceive(); // the following procedure may take some time, // so ask the terminal for some extra wait time. APDU.waitExtension(); // lookup APDU in list of known requests. byte idx = lookupRequest (); // request found? if (idx == 0) // no: append request to list appendRequest (); else { // yes: send response. sw = sendResponse (idx, apdu); if (sw != ISO7816.SW_NO_ERROR) ISOException.throwIt (sw); } return; } // throw an exception if the instruction is unknown (not handled yet). ISOException.throwIt (ISO7816.SW_INS_NOT_SUPPORTED); } //========================================================================= // Behaviour simulation //========================================================================= /** * Lookup request in list of known requests.<p> * Global param buffer byte[] - incoming request * @return byte - index of request (or 0 if not found) */ private byte lookupRequest () { // traverse list to find request entry. pos = 0; while (reqData [pos] != 0) { // get size of entry short size = (short)((reqData[pos+3] & 0xFF) + 5); // are we in correct state? if (reqData[pos+1] == 0 || state == reqData[pos+1]) { // check for identical APDUs sw = 0; for (n = 0; n < size; n++) if (buffer[n] != reqData[(short)(pos+n+4)]) { sw = size; break; } // do APDUs match? if (sw == 0) { // yes: set next state. if (reqData[pos+2] != (byte)(0xFF)) state = reqData[pos+2]; // return index return reqData [pos]; } } // try next element pos += (size+4); } // report lookup failure (unknown request) return 0; } //------------------------------------------------------------------------- /** * Add new request to the end of the list. * Global param buffer byte[] - buffer holding incoming APDU * Global param count short - number of additional data bytes */ private void appendRequest () { // we are adding to the end of the list, // so find end of current list first. pos = 0; byte idx = 0; while (reqData [pos] != 0) { // remember last valid index found. idx = reqData [pos]; // total size of entry is (9 + datalength); // 9 = sizeof(Hdr) + sizeof(APDU) pos += (short) ((reqData[pos+3] & 0xFF) + 9); } // fill in header data reqData [pos] = ++idx; // next available index reqData [pos+1] = state; // current state reqData [pos+2] = (byte)0xFF; // "no state change" reqData [pos+3] = (byte)count; // size of additional data // save content for (n = 0; n < count+5; n++) reqData [(short)(pos+n+4)] = buffer[n]; pos += (count + 9); reqData [pos] = 0; } //------------------------------------------------------------------------- /** * Send response (status, data).<p> * @param idx byte - index of response * @param apdu APDU - the incoming APDU * @return short - status code */ private short sendResponse (byte idx, APDU apdu) { // traverse list to find response entry. pos = 0; while (respData [pos] != 0) { // get type of entry and next word byte type = respData [pos+1]; n = getWord (respData, (short)(pos+2)); count = (type == 1 ? 4 : (short)(n+4)); // index found? if (respData [pos] == idx) { // check type of entry. if (type == 1) { if (n == 0) { // provoke exception apdu.setIncomingAndReceive(); apdu.setIncomingAndReceive(); } // return word as status. return n; } // send out response. apdu.setOutgoing(); apdu.setOutgoingLength (n); apdu.sendBytesLong (respData, (short)(pos+4), n); // report success. return ISO7816.SW_NO_ERROR; } // advance to next entry pos += count; } // report failure (no response found). return ISO7816.SW_DATA_INVALID; } //========================================================================= // Logger command buffer access. //========================================================================= /** * Read request/response data.<p> * @param apdu APDU - incoming APDU */ private void readData (APDU apdu) { // get addr(offset) into buffer and length // of data block to be read. buffer = apdu.getBuffer(); pos = getWord (buffer, ISO7816.OFFSET_P1); count = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF); if (count == 0) count = 256; // decide on address which target buffer to use. prepareTarget(); if (target == null) { // special treatment if we are reading 'atrAppId'. pos = (short)256; count = (short)2; target = lcData; lcData[256] = (byte)((atrAppId >> 8) & 0xFF); lcData[257] = (byte)( atrAppId & 0xFF); } // send data apdu.setOutgoing(); apdu.setOutgoingLength (count); apdu.sendBytesLong (target, pos, count); } //------------------------------------------------------------------------- /** * Write request/response data.<p>!Cyberflex * @param buffer byte[] - incoming command APDU */ private void writeData (byte[] buffer) { // get addr(offset) into buffer and length // of data block to be stored. pos = getWord (buffer, ISO7816.OFFSET_P1); count = (short)(buffer[ISO7816.OFFSET_LC] & 0xFF); if (count == 0) count = 256; // decide on address which target buffer to use. prepareTarget(); if (target == null) { atrAppId = getWord (buffer, ISO7816.OFFSET_CDATA); return; } // transfer data for (n = 0; n < count; n++) target [(short)(pos + n)] = buffer [(short)(ISO7816.OFFSET_CDATA + n)]; } //------------------------------------------------------------------------- /** * Set 'target' and 'pos' based on initial index value.<p> */ private void prepareTarget () { target = reqData; // [0000 - 07FF] Requests if (pos > LENGTH_DATA-1) { pos -= LENGTH_DATA; target = respData; // [0800 - 0FFF] Responses if (pos > LENGTH_DATA-1) { pos -= LENGTH_DATA; target = lcData; // [1000 - 10FF] LC table if (pos > 256) { pos -= 256; target = null; // [1100 - 1101] LC table } } } } //========================================================================= // Cardlet control methods //========================================================================= //------------------------------------------------------------------------- /** * Installs this applet.<p> * @param apdu APDU - install APDU * @param bOffset short - the starting offset in bArray * @param bLength byte - the length in bytes of the parameter data in bArray */ public static void install (APDU apdu) { new Logger (); } //------------------------------------------------------------------------- /** * select the applet for execution.<p> * @return boolean - true, if applet can be selected for execution. */ public boolean select () { // reset state state = (byte)0; // report success return true; } //------------------------------------------------------------------------- /** * de-select the applet.<p> */ public void deselect () { } //========================================================================= // Helper methods. //========================================================================= //------------------------------------------------------------------------- /** * Get a short value from data buffer.<p> * @param buffer byte[] - data buffer * @param pos short - offset to word * @return short - value read */ private static short getWord (byte[] buffer, short pos) { short v1 = (short)(buffer[pos ] & 0xFF); short v2 = (short)(buffer[pos+1] & 0xFF); return (short)((v1 << 8) | v2); } }