//******************************************************************/
//* 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);
}
}