package edu.mit.csail.tc; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; /** * TEM execution engine. * * This module is responsible for executing SECs (Security-Enhanced Closures). * * @author Victor Costan */ class TEMExecution { /** No SEC is loaded into the execution engine. */ public static final byte STATUS_NOSEC = 0; /** A SEC is loaded and ready to be executed. */ public static final byte STATUS_READY = 1; /** The SEC has been successfully executed. */ public static final byte STATUS_SUCCESS = 2; /** An exception has occurred while the SEC was executed. */ public static final byte STATUS_EXCEPTION = 3; /** A permanent store fault has occurred while the SEC was executed. */ public static final byte STATUS_PSFAULT = 4; /** The length of the last SEC's output. */ public static short outLength; /** The ID of the buffer holding the current SEC's output. */ public static byte outBufferIndex; /** The status of the last proc's execution. */ public static byte status; /** The SEC buffer to be used when {@link #execute()} is called. */ public static byte i_secBufferIndex; /** The initial IP value to be used when {@link #execute()} is called. */ private static short i_secIP; /** The initial SP value to be used when {@link #execute()} is called. */ private static short i_secSP; /** IF <code>true</code>, the loaded SEC allows development hooks. */ private static boolean i_devhooks; /** Keeps track of the keys authorized for use by the bound SEC. */ private static boolean[] authorizedKeys; /** The test hash used by the SECpack loader. */ private static byte[] testHash; /** Special value indicating an empty Persistent Store fault register. */ private static short PS_INVALID = (short)-1; /** Persistent Store fault register: the next slot to be used by psnew. */ private static short i_nextPSCell; /** * Initializes the TEM execution engine. * Called when the TEM is activated. */ public static void init() { status = STATUS_NOSEC; outBufferIndex = TEMBuffers.INVALID_BUFFER; i_secBufferIndex = TEMBuffers.INVALID_BUFFER; testHash = JCSystem.makeTransientByteArray(TEMCrypto.getDigestLength(), JCSystem.CLEAR_ON_DESELECT); authorizedKeys = JCSystem.makeTransientBooleanArray(TEMCrypto.NUM_KEYS, JCSystem.CLEAR_ON_DESELECT); // authorizedKeys should start out false } /** * Releases all the resources held by the TEM execution module. * Called when the TEM is deactivated. */ public static void deinit() { if (status != STATUS_NOSEC) { status = STATUS_NOSEC; // no need to release buffers, TEMBuffers.deinit() will be called later outBufferIndex = TEMBuffers.INVALID_BUFFER; i_secBufferIndex = TEMBuffers.INVALID_BUFFER; } authorizedKeys = null; testHash = null; } /** * Executes the currently bound SEC. * * For correct functionality, the engine's status should be * {@link #STATUS_READY}. */ public static void execute() { // ASSERT: status == STATUS_READY // resume execution short sp = TEMExecution.i_secSP; short ip = TEMExecution.i_secIP; byte[] pBuffer = TEMBuffers.get(TEMExecution.i_secBufferIndex); byte[] outBuffer; if (TEMExecution.outBufferIndex == TEMBuffers.INVALID_BUFFER) outBuffer = null; else { TEMBuffers.pin(TEMExecution.outBufferIndex); outBuffer = TEMBuffers.get(TEMExecution.outBufferIndex); } short outOffset = TEMExecution.outLength; // registers short opcode; short operand1 = (short)0, operand2, operand3, operand4, result; boolean condition; // execute try { while (true) { // vm block opcode = pBuffer[ip]; ip++; switch (opcode) { /**** Arithmetics ****/ case 0x10: // add case 0x11: // sub case 0x12: // mul case 0x13: // div case 0x14: // mod case 0x15: // available (rotational shift left?) case 0x16: // available (rotational shift right?) case 0x17: // available (xor?) sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); switch(opcode & 0x07) { case 0x00: result = (short)(operand1 + operand2); break; case 0x01: // sub result = (short)(operand1 - operand2); break; case 0x02: // mul result = (short)(operand1 * operand2); break; case 0x03: // div result = (short)(operand1 / operand2); break; case 0x04: // mod result = (short)(operand1 % operand2); break; default: // undefined op result = 0; } Util.setShort(pBuffer, sp, result); sp += 2; break; /**** Complex memory stream operations ****/ case 0x18: // mdfxb (message digest w/ fixed buffers) case 0x19: // mdvb (message digest w/ variable buffers) case 0x1A: // mcmpfxb (memory-compare fixed buffers) case 0x1B: // mcmpvb (memory-compare variable buffers) case 0x1C: // mcfxb (memory-copy fixed buffers) case 0x1D: // mcvb (memory-copy variable buffers) if ((opcode & 1) != 0) { sp -= 2; operand3 = Util.getShort(pBuffer, sp); sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); } else { operand1 = Util.getShort(pBuffer, ip); ip += 2; operand2 = Util.getShort(pBuffer, ip); ip += 2; operand3 = Util.getShort(pBuffer, ip); ip += 2; } if ((opcode & 4) != 0) { Util.arrayCopyNonAtomic(pBuffer, operand2, pBuffer, operand3, operand1); result = operand1; } else if ((opcode & 2) != 0) { result = Util.arrayCompare(pBuffer, operand2, pBuffer, operand3, operand1); } else { if (operand3 == -1) { result = TEMCrypto.digest(pBuffer, operand2, operand1, outBuffer, outOffset); outOffset += result; } else result = TEMCrypto.digest(pBuffer, operand2, operand1, pBuffer, operand3); } Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x1E: // rnd (generate random data) sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); if (operand2 == -1) { TEMCrypto.random(outBuffer, outOffset, operand1); outOffset += operand1; } else TEMCrypto.random(pBuffer, operand2, operand1); break; case 0x1F: // unallocated break; /**** Flow control ****/ case 0x21: // jz, je (jump if zero / equal) case 0x22: // ja, jg (if above zero / greater) case 0x23: // jae, jge (if above or equal to zero / greater or equal) case 0x24: // jb, jl (if below zero / less) case 0x25: // jbe, jle (if below or equal to zero / less or equal) case 0x26: // jnz, jne (if non-zero / equal) case 0x27: // jmp (unconditional jump) if (opcode != 0x27) { // jmp doesn't pop a stack value, everything else does sp -= 2; operand1 = Util.getShort(pBuffer, sp); } operand2 = Util.getShort(pBuffer, ip); ip += 2; condition = false; if ((opcode & 0x01) != 0) condition |= (operand1 == 0); if ((opcode & 0x02) != 0) condition |= (operand1 > 0); if ((opcode & 0x04) != 0) condition |= (operand1 < 0); if (condition) ip += operand2; break; /**** Memory access ****/ case 0x30: // ldbc (load byte constant) case 0x31: // ldwc (load word constant) case 0x32: // ldb (load byte) case 0x33: // ldw (load word) case 0x36: // ldbv (load byte from variable address) case 0x37: // ldwv (load word from variable address) if ((opcode & 0x02) != 0) { // memory load if ((opcode & 0x04) != 0) { // from variable address sp -= 2; operand2 = Util.getShort(pBuffer, sp); } else { // from fixed address operand2 = Util.getShort(pBuffer, ip); ip += 2; } } else { // constant load // NOTE: this relies on the fact that the b0 = 0 for byte and 1 for short operand2 = ip; ip += 1 + (opcode & 0x01); } if((opcode & 1) != 0) result = Util.getShort(pBuffer, operand2); else result = pBuffer[operand2]; Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x38: // stb (store byte) case 0x39: // stw (store word) case 0x3A: // stbv (store byte at variable address) case 0x3B: // stwv (store word at variable address) sp -= 2; operand1 = Util.getShort(pBuffer, sp); if ((opcode & 2) != 0) { // variable address sp -= 2; operand2 = Util.getShort(pBuffer, sp); } else { // fixed address operand2 = Util.getShort(pBuffer, ip); ip += 2; } if ((opcode & 1) != 0) Util.setShort(pBuffer, operand2, operand1); else pBuffer[operand2] = (byte)operand1; break; case 0x34: // pop sp -= 2; break; case 0x35: // popn operand1 = (short)(pBuffer[ip] << 1); ip++; sp -= operand1; break; case 0x3C: // dupn operand1 = (short)(pBuffer[ip] << 1); ip++; Util.arrayCopyNonAtomic(pBuffer, (short)(sp - operand1), pBuffer, sp, operand1); sp += operand1; break; case 0x3D: // flipn operand1 = (short)(pBuffer[ip] << 1); ip++; operand2 = (short)(sp - 2); operand1 = (short)(sp - operand1); for (; operand1 < operand2; operand1 += 2, operand2 -= 2) { operand3 = Util.getShort(pBuffer, operand1); operand4 = Util.getShort(pBuffer, operand2); Util.setShort(pBuffer, operand1, operand4); Util.setShort(pBuffer, operand2, operand3); } break; /**** Flow control 2: procedure calls. ****/ case 0x3E: // call (call procedure) operand1 = Util.getShort(pBuffer, ip); ip += 2; Util.setShort(pBuffer, sp, ip); sp += 2; ip += operand1; break; case 0x3F: // ret (return from procedure) sp -= 2; ip = Util.getShort(pBuffer, sp); break; /**** Data output ****/ case 0x40: // outfxb (output fixed buffer) case 0x41: // outvlb (output variable-length buffer) case 0x43: // outvb (output variable buffer) if ((opcode & 1) != 0) { if ((opcode & 2) != 0) { sp -= 2; operand2 = Util.getShort(pBuffer, sp); } else { operand2 = Util.getShort(pBuffer, ip); ip += 2; } sp -= 2; operand1 = Util.getShort(pBuffer, sp); } else { operand1 = Util.getShort(pBuffer, ip); ip += 2; operand2 = Util.getShort(pBuffer, ip); ip += 2; } Util.arrayCopyNonAtomic(pBuffer, operand2, outBuffer, outOffset, operand1); outOffset += operand1; break; case 0x42: // outnew (allocate output buffer) sp -= 2; operand1 = Util.getShort(pBuffer, sp); TEMExecution.outBufferIndex = TEMBuffers.create(operand1); // handler to catch running out of buffer memory if (TEMExecution.outBufferIndex == TEMBuffers.INVALID_BUFFER) ISOException.throwIt(ISO7816.SW_FILE_FULL); TEMBuffers.pin(TEMExecution.outBufferIndex); outBuffer = TEMBuffers.get(TEMExecution.outBufferIndex); break; case 0x44: // outb (output byte) sp -= 2; operand1 = Util.getShort(pBuffer, sp); outBuffer[outOffset] = (byte)operand1; outOffset++; break; case 0x45: // outw (output short) sp -= 2; operand1 = Util.getShort(pBuffer, sp); Util.setShort(outBuffer, outOffset, operand1); outOffset += 2; break; case 0x46: // halt // save the results and exit TEMBuffers.unpin(outBufferIndex); TEMExecution.outLength = outOffset; TEMExecution.status = STATUS_SUCCESS; return; case 0x47: // psrm (remove persistent store location) sp -= 2; operand1 = Util.getShort(pBuffer, sp); TEMStore.releaseCell(pBuffer, operand1); break; /**** Persistent store operations ****/ case 0x48: // psupfxb (update persistent store, fixed buffers) case 0x49: // psupvb (update persistent store, variable buffers) case 0x4A: // pswrfxb (write persistent store, fixed buffers) case 0x4B: // pswrvb (write persistent store, variable buffers) case 0x4C: // psrdfxb (read persistent store, fixed buffers) case 0x4D: // psrdvb (read persistent store, variable buffers) if ((opcode & 0x01) != 0) { sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); } else { operand1 = Util.getShort(pBuffer, ip); ip += 2; operand2 = Util.getShort(pBuffer, ip); ip += 2; } if ((opcode & 0x04) != 0 && operand2 == -1) { condition = TEMStore.readOrWrite(pBuffer, operand1, outBuffer, outOffset, (opcode & 4) != 0, (opcode & 2) != 0); result = condition ? TEMStore.VALUE_SIZE : (short)0; outOffset += result; } else { condition = TEMStore.readOrWrite(pBuffer, operand1, pBuffer, operand2, (opcode & 4) != 0, (opcode & 2) != 0); result = condition ? TEMStore.VALUE_SIZE : (short)0; } if (condition == false) { // Abort execution if reading or updating blank cell, or // creating but the pstore is full. ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND); } Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x4E: // pshkfxb (persistent store has key, fixed buffers) case 0x4F: // pshkvb (persistent store has key, variable buffers) if ((opcode & 0x01) != 0) { sp -= 2; operand1 = Util.getShort(pBuffer, sp); } else { operand1 = Util.getShort(pBuffer, ip); ip += 2; } result = (short)((TEMStore.findCell(pBuffer, operand1) != TEMStore.INVALID_CELL) ? 1 : 0); Util.setShort(pBuffer, sp, result); sp += 2; break; /**** Crypto ****/ case 0x50: // kefxb (key-encrypt with fixed buffers) case 0x51: // kevb (key-encrypt with variable buffers) case 0x52: // kdfxb (key-decrypt with fixed buffers) case 0x53: // kdvb (key-decrypt with variable buffers) case 0x54: // ksfxb (key-sign with fixed buffers) case 0x55: // ksvb (key-sign with variable buffers) case 0x56: // kvsfxb (key-verify signature with fixed buffers) case 0x57: // kvsvb (key-verify signature with variable buffers) if((opcode & 1) != 0) { sp -= 2; operand3 = Util.getShort(pBuffer, sp); sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); } else { operand1 = Util.getShort(pBuffer, ip); ip += 2; operand2 = Util.getShort(pBuffer, ip); ip += 2; operand3 = Util.getShort(pBuffer, ip); ip += 2; } sp -= 2; operand4 = Util.getShort(pBuffer, sp); if (authorizedKeys[operand4] == false) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); if (operand3 == -1) { if ((opcode & 0x04) == 0) result = TEMCrypto.cryptWithKey((byte)operand4, pBuffer, operand2, operand1, outBuffer, outOffset, ((opcode & 2) == 0)); else result = TEMCrypto.signWithKey((byte)operand4, pBuffer, operand2, operand1, outBuffer, outOffset, ((opcode & 2) == 0)); outOffset += result; } else { if ((opcode & 0x04) == 0) result = TEMCrypto.cryptWithKey((byte)operand4, pBuffer, operand2, operand1, pBuffer, operand3, ((opcode & 2) == 0)); else result = TEMCrypto.signWithKey((byte)operand4, pBuffer, operand2, operand1, pBuffer, operand3, ((opcode & 2) == 0)); } Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x5A: // rdk (read key) sp -= 2; operand1 = Util.getShort(pBuffer, sp); result = TEMCrypto.loadKey(pBuffer, operand1); if(result != TEMCrypto.INVALID_KEY) authorizedKeys[result] = true; Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x58: // ldkel (load key encryption length) case 0x5B: // stk (store key) sp -= 2; operand2 = Util.getShort(pBuffer, sp); sp -= 2; operand1 = Util.getShort(pBuffer, sp); if(authorizedKeys[operand1] == false) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); if (opcode == 0x58) { // ldkel result = TEMCrypto.getEncryptedDataSize((byte)operand1, operand2); } else { if(operand2 == (short)-1) { // stk result = TEMCrypto.saveKey((byte)operand1, outBuffer, outOffset); outOffset += result; } else result = TEMCrypto.saveKey((byte)operand1, pBuffer, operand2); } Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x5C: // relk (release key) sp -= 2; operand1 = Util.getShort(pBuffer, sp); if(authorizedKeys[operand1] == false) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); TEMCrypto.releaseKey((byte)operand1); break; case 0x5D: // ldkl (load key length) sp -= 2; operand1 = Util.getShort(pBuffer, sp); if(authorizedKeys[operand1] == false) ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED); result = TEMCrypto.getKeyLength((byte)operand1); Util.setShort(pBuffer, sp, result); sp += 2; break; case 0x5E: // genkp (generate key / key pair) operand1 = pBuffer[ip]; ip++; result = TEMCrypto.generateKey(operand1 == 0); operand2 = (short)(result >> 8); operand3 = (short)(result & 0xff); if(operand2 != TEMCrypto.INVALID_KEY) authorizedKeys[operand2] = true; if(operand1 == 0 && operand3 != TEMCrypto.INVALID_KEY) authorizedKeys[operand3] = true; Util.setShort(pBuffer, sp, operand2); sp += 2; if (operand1 == 0) { Util.setShort(pBuffer, sp, operand3); sp += 2; } break; case 0x5F: // authk (authorize key) sp -= 2; operand1 = Util.getShort(pBuffer, sp); operand2 = Util.getShort(pBuffer, ip); ip += 2; if(authorizedKeys[operand1]) { // key already authorized, SEC can set/change its authorization TEMCrypto.setKeyAuth((byte)operand1, pBuffer, operand2); result = operand1; } else { // SEC trying to get authorization to use key if(TEMCrypto.verifyKeyAuth((byte)operand1, pBuffer, operand2)) { authorizedKeys[operand1] = true; result = operand1; } else result = (short)-1; } Util.setShort(pBuffer, sp, result); sp += 2; break; } } } catch (Exception e) { // developer "production" mode // catch(APDUException e) { // DEBUG MODE -- trick that invalidates this line and traps the debugger status = STATUS_EXCEPTION; // for developers: save the SEC trace (production TEMs don't need this) TEMExecution.i_secIP = ip; TEMExecution.i_secSP = sp; TEMExecution.outLength = outOffset; } } /** * Binds the SEC contained in a SECpack to the execution engine. * * After this call succeeds, the SEC can be executed by calling * {@link #execute()}. For correct functionality, * {@link #bindSecPack(byte, byte)} should only be called when the engine's * status is {@link #STATUS_NOSEC}. * * The SECpack is decoded in-place, so the buffer received should be large * enough to hold the decoded SECpack. * * @param keyIndex the ID of a key that can decrypt the SECpack * @param secPackIndex the ID of the buffer containing the SECpack whose SEC * will be decrypted and bound * @return <code>true</code> if unpacking succeeded, or <code>false</code> if * the SECpack unpacking failed (perhaps */ public static boolean bindSecPack(byte keyIndex, byte secPackIndex) { // ASSERT: status == STATUS_NOSEC byte[] secPack = TEMBuffers.get(secPackIndex); short secPackLength = TEMBuffers.size(secPackIndex); // Refuse SECpacks using incompatible formats. if (secPack[0] != (byte)1) return false; // Pick up the header parts that are interesting. TEMExecution.i_secSP = Util.getShort(secPack, (short)8); TEMExecution.i_secIP = Util.getShort(secPack, (short)10); TEMExecution.i_devhooks = (byte)(secPack[1] & (byte)1) != (byte)0; // Compute sizes for all SECimage parts. short headerSize = TEMCrypto.getDigestBlockLength(); short frozenSize = Util.getShort(secPack, (short)2); short privateSize = Util.getShort(secPack, (short)4); short zerosSize = Util.getShort(secPack, (short)6); short cryptedSize = (frozenSize != 0 || privateSize != 0) ? TEMCrypto.getEncryptedDataSize(keyIndex, (short)(privateSize + TEMCrypto.getDigestLength())) : 0; short securedPackSize = (short)(frozenSize + cryptedSize + headerSize); short plainPackSize = (short)(secPackLength - securedPackSize); if (cryptedSize != 0) { // The SEC image must be able to hold the signature, since we'll be // dumping it there temporarily. if((short)(zerosSize + plainPackSize) < TEMCrypto.getDigestLength()) zerosSize = (short)(TEMCrypto.getDigestLength() - plainPackSize); } // Save the header for signature checking. Util.arrayCopyNonAtomic(secPack, (short)0, testHash, (short)0, headerSize); // Put together the SEC image. Util.arrayCopyNonAtomic(secPack, headerSize, secPack, (short)0, frozenSize); short secOffset = frozenSize; if (frozenSize != 0 || privateSize != 0) { // Decrypt the secret part and check the signature TEMCrypto.cryptWithKey(keyIndex, secPack, (short)(frozenSize + headerSize), cryptedSize, secPack, frozenSize, false); secOffset += privateSize; TEMCrypto.digest2(testHash, (short)0, headerSize, secPack, (short)0, secOffset, testHash, (short)0); if (Util.arrayCompare(testHash, (short)0, secPack, secOffset, (short)testHash.length) != 0) { // Signature check failed. // TODO: set better exception checking return false; } } Util.arrayCopyNonAtomic(secPack, securedPackSize, secPack, secOffset, plainPackSize); Util.arrayFillNonAtomic(secPack, (short)(secOffset + plainPackSize), (short)(secPackLength - secOffset - plainPackSize), (byte)0); // Unpacking succeeded, set the SEC execution context. TEMExecution.i_secBufferIndex = secPackIndex; TEMExecution.i_nextPSCell = PS_INVALID; TEMExecution.outLength = 0; TEMExecution.status = STATUS_READY; return true; } /** * Unbinds the currently bound SEC from the engine. * * This releases the resources associated with the bound SEC, and prepares the * engine for accepting another SECpack. */ public static void unbindSec() { // Drop the SEC buffer TEMBuffers.unpin(TEMExecution.i_secBufferIndex); TEMBuffers.release(TEMExecution.i_secBufferIndex); TEMExecution.i_secBufferIndex = TEMBuffers.INVALID_BUFFER; // Drop the volatile (non-persistent) SEC keys TEMCrypto.releaseVolatileKeys(); for (short i = 0; i < authorizedKeys.length; i++) authorizedKeys[i] = false; if (TEMExecution.status != STATUS_SUCCESS) { // Since the SEC didn't execute well, discard any output. if (TEMExecution.outBufferIndex != TEMBuffers.INVALID_BUFFER) { TEMBuffers.unpin(TEMExecution.outBufferIndex); TEMBuffers.release(TEMExecution.outBufferIndex); } } TEMExecution.outBufferIndex = TEMBuffers.INVALID_BUFFER; TEMExecution.status = STATUS_NOSEC; } /** The version of the trace output format. */ private static final short TRACE_VERSION = (short)0x01; /** * Produces a trace of the current SEC status. * * @param buffer the buffer that the trace will be written to * @param offset the offset of the first byte in the buffer that will receive * the trace * @return the length of the trace produced */ public static final short devTrace(byte[] buffer, short offset) { if (i_devhooks == false) return 0; Util.setShort(buffer, offset, TRACE_VERSION); Util.setShort(buffer, (short)(offset + 2), TEMExecution.i_secSP); Util.setShort(buffer, (short)(offset + 4), TEMExecution.i_secIP); Util.setShort(buffer, (short)(offset + 6), TEMExecution.outLength); Util.setShort(buffer, (short)(offset + 8), TEMExecution.i_nextPSCell); return (short)10; } /** * Fixes a Persistent Store fault. * * This is called when the driver responds to a Persistent Store fault. The * fault is fixed accoding to the given instructions, and the execution engine * becomes ready to resume SEC execution. * * @param nextCell the next PStore cell to be used by psnew * */ public static final void solvePStoreFault(short nextCell) { // ASSERT: status == STATUS_PSFAULT TEMExecution.i_nextPSCell = nextCell; TEMExecution.status = STATUS_READY; } }