package edu.mit.csail.tc;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
/**
* TEM communication module.
* @author Victor Costan
*
* For this TEM implementation, the communication module is the JavaCard Applet.
* The communication module processes commands from the outside, and converts
* them into calls to the appropriate modules.
*
* In the JavaCard world, commands are APDUs (we don't use the JavaCard RMI
* mechanism).
*
* This implementation provides "test points" for the crypto engine. While the
* test points help development a lot, they should be removed from production
* implementations.
*/
public class TEMApplet extends Applet {
/** The firmware version. */
public static final short FIRMWARE_VER = 0x0110;
public static void install(byte[] bArray, short bOffset, byte bLength) {
// GP-compliant JavaCard applet registration
new TEMApplet()
.register(bArray, (short) (bOffset + 1), bArray[bOffset]);
}
/**
* Empty constructor.
*
* This is called when the applet is installed, and we don't need to do
* anything useful at that point.
* The real initialization happens when the activate APDU is received.
*/
public TEMApplet() { }
private static void sendSuccess(APDU apdu) {
apdu.setOutgoingAndSend((short)0, (short)0);
}
private static void sendSuccessAndByte(APDU apdu, byte byteValue) {
byte[] buf = apdu.getBuffer();
buf[0] = byteValue;
apdu.setOutgoingAndSend((short)0, (short)1);
}
private static void sendSuccessAndShort(APDU apdu, short shortValue) {
byte[] buf = apdu.getBuffer();
Util.setShort(buf, (short)0, shortValue);
apdu.setOutgoingAndSend((short)0, (short)2);
}
private static void sendSuccessAndByteShort(APDU apdu, byte byteValue,
short shortValue) {
byte[] buf = apdu.getBuffer();
buf[0] = byteValue;
Util.setShort(buf, (short)1, shortValue);
apdu.setOutgoingAndSend((short)0, (short)3);
}
public void process(APDU apdu) {
// Good practice: Return 9000 on SELECT
if (selectingApplet()) {
return;
}
byte[] buf = apdu.getBuffer();
byte[] temBuffer; byte bufferIndex; short bufferSize;
byte[] outBuffer; byte outBufferIndex; short outputLength;
short counterIndex;
byte keyIndex;
switch (buf[ISO7816.OFFSET_INS]) {
///////////////// LIFECYCLE ///////////////////////////
case 0x10:
/**
* INS 0x10 -- Activate TEM
* Parameters:
* none
* Returns:
* nothing
* Throws:
* 69 86 (command not allowed) -- if the tag has not been set
* Remarks:
* this should be called in the factory to initialize the TEM
*/
if (TEMBuffers.init() == false)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
TEMTag.init();
TEMCrypto.init();
TEMStore.init();
TEMExecution.init();
TEMBuffers.layout();
JCSystem.requestObjectDeletion();
TEMApplet.sendSuccess(apdu);
break;
case 0x11:
/**
* INS 0x11 -- Kill TEM
* Parameters:
* none
* Returns:
* nothing
* Throws:
* 69 86 (command not allowed) -- if the tag has not been set
* Remarks:
* this renders a TEM useless and requires re-issuing
* the TEM applet can be uninstalled once this is called
*/
if (TEMBuffers.deinit() == false)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
TEMExecution.deinit();
TEMStore.deinit();
TEMCrypto.deinit();
TEMTag.deinit();
JCSystem.requestObjectDeletion();
TEMApplet.sendSuccess(apdu);
break;
case 0x12:
/**
* INS 0x12 -- Retrieve TEM firmware version
* Parameters:
* none:
* Returns:
*
*/
TEMApplet.sendSuccessAndShort(apdu, TEMApplet.FIRMWARE_VER);
break;
///////////////// RESOURCE MANAGEMENT ///////////////////////////
case 0x20:
/**
* INS 0x20 -- Allocate buffer
* Parameters:
* (P1, P2) -- buffer size
* Returns:
* byte -- buffer ID (0xFF for failure)
* Throws:
* 6A 84 (file full) -- if there's not enough memory for the buffer
*/
bufferSize = Util.getShort(buf, ISO7816.OFFSET_P1);
bufferIndex = TEMBuffers.create(bufferSize);
if (bufferIndex == TEMBuffers.INVALID_BUFFER)
ISOException.throwIt(ISO7816.SW_FILE_FULL);
TEMApplet.sendSuccessAndByte(apdu, bufferIndex);
break;
case 0x21:
/**
* INS 0x21 -- Release buffer
* Parameters:
* P1 -- buffer ID
* Returns:
* nothing
* Throws:
* nothing -- an invalid buffer ID is a NOP
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
TEMBuffers.release(bufferIndex);
TEMApplet.sendSuccess(apdu);
break;
case 0x22:
/**
* INS 0x22 -- Get buffer length
* Parameters:
* P1 -- buffer ID
* Returns:
* short -- buffer length
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid buffer ID
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
if (!TEMBuffers.isPublic(bufferIndex) ||
TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
TEMApplet.sendSuccessAndShort(apdu, TEMBuffers.size(bufferIndex));
TEMBuffers.unpin(bufferIndex);
break;
case 0x23:
/**
* INS 0x23 -- Read buffer chunk
* Parameters:
* P1 -- buffer ID
* P2 -- chunk number (0-based, each chunk is TEMBuffers.chunkSize bytes)
* Returns:
* ? bytes -- the requested chunk (exactly TEMBuffers.chunkSize bytes,
* unless this is the last chunk)
* Throws:
* 6A 86 (incorrect P1P2) -- if the buffer ID or chunk number is invalid
* Remarks:
* it is possible to get a 0 bytes return if the buffer fits in N chunks
* and P2 == N; this facilitates reading the buffer w/o needing to query
* its length separately
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
short bufferOffset = (short)(buf[ISO7816.OFFSET_P2] *
TEMBuffers.chunkSize);
temBuffer = (TEMBuffers.isPublic(bufferIndex) && TEMBuffers.pin(
bufferIndex)) ? TEMBuffers.get(bufferIndex) : null;
bufferSize = TEMBuffers.size(bufferIndex);
if (temBuffer == null || bufferOffset > bufferSize)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
apdu.setOutgoing();
outputLength = (bufferSize - bufferOffset >= TEMBuffers.chunkSize) ?
TEMBuffers.chunkSize : (short)(bufferSize - bufferOffset);
apdu.setOutgoingLength(outputLength);
apdu.sendBytesLong(temBuffer, bufferOffset, outputLength);
TEMBuffers.unpin(bufferIndex);
break;
case 0x24:
/**
* INS 0x24 -- Write buffer chunk
* Parameters:
* P1 -- buffer ID
* P2 -- chunk number (0-based, each chunk is TEMBuffers.chunkSize bytes)
* Lc -- number of bytes to write to the chunk (should be
* TEMBuffers.chunkSize, unless this is the last last chunk)
* Lc bytes -- the chunk data
* Returns:
* nothing
* Throws:
* 6A 86 (incorrect P1P2) -- if the buffer ID or chunk number is invalid
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
bufferOffset = (short)(buf[ISO7816.OFFSET_P2] * TEMBuffers.chunkSize);
bufferSize = apdu.setIncomingAndReceive();
temBuffer = (TEMBuffers.isPublic(bufferIndex) && TEMBuffers.pin(
bufferIndex)) ? TEMBuffers.get(bufferIndex) : null;
if (temBuffer == null ||
(bufferOffset + bufferSize > TEMBuffers.size(bufferIndex)))
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA,
temBuffer, bufferOffset, bufferSize);
TEMBuffers.unpin(bufferIndex);
TEMApplet.sendSuccess(apdu);
break;
case 0x25:
/**
* INS 0x25 -- Reset and get buffer chunk length.
* Parameters:
* none
* Returns:
* short -- the length, in bytes, of a buffer chunk
*/
TEMBuffers.guessChunkSize();
TEMApplet.sendSuccessAndShort(apdu, TEMBuffers.chunkSize);
break;
case 0x26:
/**
* INS 0x26 -- Releases all the TEM buffers.
* Parameters:
* none
* Returns:
* nothing
*/
TEMBuffers.releaseAll();
TEMApplet.sendSuccess(apdu);
break;
case 0x27:
/**
* INS 0x27 -- Stat the TEM keys or buffers.
* Parameters:
* P1 -- 0 for buffers, 1 for keys
* Returns:
* the state of the TEM buffers
* 3 shorts - available memory
* (PERMANENT, CLEAR_ON_RESET, CLEAR_ON_DESELECT)
* 4 bytes for each buffer entry
* byte - buffer type
* 0: NOT_A_TRANSIENT_OBJECT
* 1: CLEAR_ON_RESET
* 2: CLEAR_ON_DESELECT
* 0x40: set if the buffer is taken
* 0x80: set if the buffer is pinned
* short - requested buffer length (bytes)
* short - buffer length (bytes)
* the state of the TEM keys
* 4 bytes for each allocated key
* byte - key ID
* byte - key type
* 0x99: SYMMETRIC_KEY
* 0x55: ASYMMETRIC_PUBKEY
* 0xAA: ASYMMETRIC_PRIVKEY
* short - key length (bits)
*/
if (buf[ISO7816.OFFSET_P1] == 0)
bufferSize = TEMBuffers.stat(buf, (short)0);
else
bufferSize = TEMCrypto.stat(buf, (short)0);
apdu.setOutgoingAndSend((short)0, bufferSize);
break;
case 0x28:
/**
* INS 0x28 -- Release key
* Parameters:
* P1 -- key ID
* Returns:
* nothing
* Throws:
* nothing -- an invalid key ID is a NOP
*/
// NOTE: this key-related method must be implemented on production TEMs to
// prevent SEClosure DOSing by filling the key store
keyIndex = buf[ISO7816.OFFSET_P1];
TEMCrypto.releaseKey(keyIndex);
TEMApplet.sendSuccess(apdu);
break;
///////////////// TAG ///////////////////////////
case 0x30:
/**
* INS 0x30 -- Set tag
* Parameters:
* P1 -- buffer ID of the buffer containing the tag data
* Returns:
* nothing
* Throws:
* 69 86 (command not allowed) -- the tag has already been set
* 6A 84 (file full) -- there is not enough memory for the tag
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
if (!TEMBuffers.isPublic(bufferIndex) ||
TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_FILE_FULL);
temBuffer = TEMBuffers.get(bufferIndex);
TEMTag.set(temBuffer, (short)0, TEMBuffers.size(bufferIndex));
TEMBuffers.unpin(bufferIndex);
TEMApplet.sendSuccess(apdu);
break;
case 0x31:
/**
* INS 0x31 -- Get tag length
* Parameters:
* none
* Returns:
* short -- tag length
* Throws:
* 69 86 (command not allowed) -- the tag has not been set yet
*/
if (TEMTag.tag != null)
TEMApplet.sendSuccessAndShort(apdu, (short)TEMTag.tag.length);
else
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
break;
case 0x32:
/**
* INS 0x23 -- Read tag data
* Parameters:
* P1 -- buffer ID of the buffer which receives data
* P2 -- blank
* LC -- 4
* DATA --
* 2 bytes: byte offset of the read operation
* 2 bytes: length (bytes) of the read operation
* Returns:
* nothing
* Throws:
* 69 86 (command not allowed) -- if the tag has not been set
* 6A 86 (incorrect P1P2) -- if given an invalid buffer ID
* Remarks:
* it is possible to get a 0 bytes return if the buffer fits in N chunks
* and P2 == N; this facilitates reading the buffer w/o needing to query
* its length separately
*/
if (TEMTag.tag == null)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
bufferIndex = buf[ISO7816.OFFSET_P1];
bufferOffset = Util.getShort(buf, ISO7816.OFFSET_CDATA);
bufferSize = Util.getShort(buf, (short)(ISO7816.OFFSET_CDATA + (short)2));
temBuffer = (TEMBuffers.isPublic(bufferIndex) && TEMBuffers.pin(
bufferIndex)) ? TEMBuffers.get(bufferIndex) : null;
if(temBuffer == null || (bufferSize > TEMBuffers.size(bufferIndex)))
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
Util.arrayCopyNonAtomic(TEMTag.tag, bufferOffset,
temBuffer, (short)0, bufferSize);
TEMBuffers.unpin(bufferIndex);
TEMApplet.sendSuccess(apdu);
break;
///////////////// SEC EXECUTION ///////////////////////////
case 0x50:
/**
* INS 0x50 -- Load SECpack
* Parameters:
* P1 -- buffer ID of the buffer containing the SECpack
* P2 -- ID of the key that can decrypt the SECpack
* (ignored for unencrypted SECpacks)
* Returns:
* byte -- 1 if the SECpack is accepted, 0 otherwise
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid buffer ID
*/
if (TEMExecution.status != TEMExecution.STATUS_NOSEC)
TEMExecution.unbindSec();
bufferIndex = buf[ISO7816.OFFSET_P1];
keyIndex = buf[ISO7816.OFFSET_P2];
if (TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
TEMExecution.bindSecPack(keyIndex, bufferIndex);
TEMBuffers.unpin(bufferIndex);
if (TEMExecution.status != TEMExecution.STATUS_READY)
TEMBuffers.release(bufferIndex);
TEMApplet.sendSuccessAndByte(apdu, ((TEMExecution.status ==
TEMExecution.STATUS_READY) ? (byte)1 : (byte)0));
break;
case 0x51:
/**
* INS 0x51 -- Unbind SEC
* Parameters:
* none
* Returns:
* if the SEC executed succesfully
* byte -- buffer ID of a buffer containing the SEC output
* short -- number of bytes in the SEC output
* (the buffer might be bigger, and padded w/ garbage)
* else
* nothing
* Throws:
* 69 86 (command not allowed) -- execution engine not in the right state
* Remarks:
* the buffer containing the SEC is released before the SEC is executed
*/
keyIndex = TEMExecution.status; // abusing keyIndex to mean secStatus
outBufferIndex = TEMExecution.outBufferIndex;
outputLength = TEMExecution.outLength;
TEMExecution.unbindSec();
if (keyIndex == TEMExecution.STATUS_SUCCESS)
TEMApplet.sendSuccessAndByteShort(apdu, outBufferIndex, outputLength);
else
TEMApplet.sendSuccess(apdu);
break;
case 0x52:
/**
* INS 0x52 -- Execute bound SEC
* Parameters:
* none
* Returns:
* byte -- status of the execution engine after the SEC execution
* Throws:
* 6A 86 (incorrect P1P2) -- invalid buffer ID
* 69 86 (command not allowed) -- execution engine not in the right state
*/
if (TEMExecution.status != TEMExecution.STATUS_READY ||
TEMBuffers.pin(TEMExecution.i_secBufferIndex) == false)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
TEMExecution.execute();
TEMBuffers.unpin(TEMExecution.i_secBufferIndex);
TEMApplet.sendSuccessAndByte(apdu, TEMExecution.status);
break;
case 0x53:
/**
* INS 0x53 -- Solve Persistent Store fault
* Parameters:
* short -- the next cell to be used by psnew
* Returns:
* nothing
* Throws:
* 69 86 (command not allowed) -- execution engine not in the right state
* Remarks:
* this is a no-op if the SECpack containing the SEC isn't appropriately
* flagged; this command is only implemented on dev TEMs
*/
if (TEMExecution.status != TEMExecution.STATUS_PSFAULT)
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
// abusing outputLength to mean nextCell
outputLength = Util.getShort(buf, ISO7816.OFFSET_P1);
TEMExecution.solvePStoreFault(outputLength);
TEMApplet.sendSuccess(apdu);
break;
case (byte)0x54:
/**
* INS 0x54 -- Stat the execution engine
* Parameters:
* nothing
* Returns:
* the trace (may be empty if the SECpack doesn't allow tracing)
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid buffer ID
* 69 86 (command not allowed) -- if the execution engine is not in the right state
* Remarks:
* this is a no-op if the SECpack containing the SEC isn't appropriately flagged
* this command is only implemented on dev TEMs
*/
bufferSize = TEMExecution.devTrace(buf, (short)0);
apdu.setOutgoingAndSend((short)0, bufferSize);
break;
///////////////// CRYPTO DEBUGGING HOOKS ///////////////////////////
case 0x40:
/**
* INS 0x40 -- Generate Key or Key Pair
* Parameters:
* P1 -- key type (0x00 PKS key pair, 0x80 symmetric key)
* Returns:
* byte -- key ID of private key
* byte -- key ID of public key (0 for symmetric keys)
* Throws:
* 6A 84 (file full) -- if there's not enough memory for the keys
*/
// generate key pair
counterIndex = TEMCrypto.generateKey(buf[ISO7816.OFFSET_P1] == 0x00);
// counterIndex is abused to hold (privKeyIndex, pubKeyIndex)
TEMApplet.sendSuccessAndShort(apdu, counterIndex);
break;
case 0x41:
/**
* INS 0x41 -- Load key
* Parameters:
* P1 -- buffer ID of the buffer containing key data
* Returns:
* byte - key ID of the loaded key
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid buffer ID
* 6A 84 (file full) -- if there's not enough memory for the key
*/
bufferIndex = buf[ISO7816.OFFSET_P1];
if (TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
temBuffer = TEMBuffers.get(bufferIndex);
keyIndex = TEMCrypto.loadKey(temBuffer, (short)0);
TEMApplet.sendSuccessAndByte(apdu, keyIndex);
break;
case 0x42:
/**
* INS 0x42 -- Save key
* Parameters:
* P1 -- key ID of the key to be saved
* Returns:
* byte - buffer ID of buffer containing key material
* short - number of bytes in the output key
* (the buffer may be bigger, and padded w/ garbage)
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid key ID
* 6A 84 (file full) -- if there's not enough memory for the buffer
*/
keyIndex = buf[ISO7816.OFFSET_P1];
bufferSize = TEMCrypto.getKeyLength(keyIndex);
bufferIndex = TEMBuffers.create(bufferSize);
if (TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
temBuffer = TEMBuffers.get(bufferIndex);
outputLength = TEMCrypto.saveKey(keyIndex, temBuffer, (short)0);
TEMApplet.sendSuccessAndByteShort(apdu, bufferIndex, outputLength);
break;
case 0x43:
/**
* INS 0x43 -- Encrypt data
* Parameters:
* P1 -- key ID of the encryption key
* P2 -- buffer ID of the buffer containing the data to be encrypted
* Returns:
* byte - buffer ID of buffer containing encrypted data
* short -- number of bytes in the encrypted output
* (the buffer may be bigger, padded w/ garbage)
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid key ID or buffer ID
* 6A 84 (file full) -- if there's not enough memory for the buffer
* Remarks:
* the input buffer is not released automatically
*/
case 0x44:
/**
* INS 0x44 -- Decrypt data
* Parameters:
* P1 -- key ID of the encryption key
* P2 -- buffer ID of the buffer containing the data to be decrypted
* Returns:
* byte - buffer ID of buffer containing decrypted data
* short -- number of bytes in the decrypted output
* (the buffer may be bigger, amd padded w/ garbage)
* Throws:
* 6A 86 (incorrect P1P2) -- if given an invalid key ID or buffer ID
* 6A 84 (file full) -- if there's not enough memory for the buffer
* Remarks:
* the input buffer is not released automatically
*/
keyIndex = buf[ISO7816.OFFSET_P1];
bufferIndex = buf[ISO7816.OFFSET_P2];
if (TEMBuffers.pin(bufferIndex) == false)
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
temBuffer = TEMBuffers.get(bufferIndex);
boolean encrypting = (buf[ISO7816.OFFSET_INS] == 0x43);
bufferSize = TEMBuffers.size(bufferIndex);
outputLength = (encrypting) ?
TEMCrypto.getEncryptedDataSize(keyIndex, bufferSize) : bufferSize;
outBufferIndex = TEMBuffers.create(outputLength);
TEMBuffers.pin(outBufferIndex);
outBuffer = TEMBuffers.get(outBufferIndex);
if (outBuffer == null)
ISOException.throwIt(ISO7816.SW_FILE_FULL);
outputLength = TEMCrypto.cryptWithKey(keyIndex, temBuffer, (short)0,
bufferSize, outBuffer, (short)0,
encrypting);
TEMBuffers.unpin(outBufferIndex);
TEMBuffers.unpin(bufferIndex);
TEMApplet.sendSuccessAndByteShort(apdu, outBufferIndex, outputLength);
break;
default:
// good practice: If you don't know the INStruction, say so:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
}
}