package edu.mit.csail.tc;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.DESKey;
import javacard.security.Key;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.MessageDigest;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;
/**
* TEM cryptographic engine.
* @author Victor Costan
*
* The TEM cryptographic engine exposes a uniform interface on top of the
* cryptographic accelerator inside the TEM implementation. Its duties include
* generating, using, and importing/exporting cryptographic keys, and computing
* message digests.
*/
class TEMCrypto {
/** The number of entries in the keys file. */
public static final short NUM_KEYS = 8;
/** The size of a key authorization secret. */
public static final short AUTH_SIZE = 20;
/** Invalid key ID. */
public static final byte INVALID_KEY = (byte)-1;
/** Invalid pair of key IDs. */
public static final short INVALID_KEY_PAIR = (short)-1;
/** The identifier for symmetric keys. */
public static final byte SYMMETRIC_KEY = (byte)0x99;
/** The identifier for the public component of an asymmetric key. */
public static final byte ASYMMETRIC_PUBKEY = (byte)0xAA;
/** The identifier for the private component of an asymmetric key. */
public static final byte ASYMMETRIC_PRIVKEY = (byte)0x55;
/** The cipher to be used for symmetric key encryption/decryption. */
private static final byte SKS_CIPHER_ID = Cipher.ALG_DES_CBC_ISO9797_M2;
/** The cipher to be used for PKS encryption/decryption. */
// Use this when JCOP supports OAEP padding according to PKCS#1 v2.0
// private static final byte pksCipherID = Cipher.ALG_RSA_PKCS1_OAEP;
// PKCS#1 v1.5 padding
private static final byte PKS_CIPHER_ID = Cipher.ALG_RSA_PKCS1;
/** The number of padding bytes needed for PKS encryption/decryption. */
// Padding size: 11 for PKCS#1 v1.5, 41 for PKCS#1 v2.0
// (according to http://www.openssl.org/docs/crypto/RSA_public_encrypt.html)
// Our tests: 11 works for v1.5, but 41 fails for v2.0; so we use 42
// private static final short pksCipherPadding = 42;
private static final short pksCipherPadding = 11;
/** The size of the generic key TEM header. */
private static final short KEY_HEADER_BYTES = 1;
/**
* Message digest instance.
*
* Static because the dynamic version leaks memory.
*/
private static MessageDigest digest;
/**
* Symmetric / asymmetric cipher instances.
*
* Static because the dynamic versions leak memory.
*/
private static Cipher symCipher, asymCipher;
/**
* Symmetric / asymmetric signature instances.
*
* Static because the dynamic versions leak memory.
*/
private static Signature symSignature, asymSignature;
/**
* Randomizer instance.
*
* Static because the dynamic version leaks memory.
*/
private static RandomData randomizer;
/** Holds random material for creating symmetric keys. */
private static byte[] randomMaterial;
/** The key file (parallel: register file). */
private static Key[] keys;
/** The key authorization secrets. */
private static byte[] authorizations;
/**
* Indicates which keys are persistent.
*
* Persistent keys are retained after the proc that created them finishes.
* A key becomes persistent when its authorization value is set.
*/
private static boolean[] persistent;
/**
* Initializes the TEM cryptographic engine.
*
* Called when the TEM is activated.
*/
public static final void init() {
keys = new Key[NUM_KEYS];
authorizations = new byte[(short)(NUM_KEYS * AUTH_SIZE)];
persistent = new boolean[NUM_KEYS];
for (short i = 0; i < persistent.length; i++)
persistent[i] = false;
symCipher = Cipher.getInstance(SKS_CIPHER_ID, false);
asymCipher = Cipher.getInstance(PKS_CIPHER_ID, false);
digest = MessageDigest.getInstance(MessageDigest.ALG_SHA, false);
digest.reset();
randomizer = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
symSignature = Signature.getInstance(Signature.ALG_DES_MAC8_ISO9797_M2,
false);
asymSignature = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
randomMaterial = JCSystem.makeTransientByteArray((short)16,
JCSystem.CLEAR_ON_DESELECT);
}
/**
* Releases all the resources held by the TEM cryptographic engine.
*
* Called when the TEM is de-activated.
*/
public static final void deinit() {
for (short i = 0; i < keys.length; i++) {
if (keys[i] != null)
keys[i].clearKey();
}
keys = null;
authorizations = null;
persistent = null;
symCipher = null; asymCipher = null;
digest = null; randomizer = null;
symSignature = null; asymSignature = null;
randomMaterial = null;
}
/**
* Identifies an available key slot.
* This does not mark the slot busy (allocation), it merely identifies it.
* @return a key slot that is available, or -1 if all the slots are taken
*/
private static final byte findFreeKeySlot() {
for (byte i = (byte)0; i < keys.length; i++) {
if (keys[i] == null)
return i;
}
return INVALID_KEY;
}
/**
* Generates an encryption key (or key pair, for PKS).
* @param keyIsAsymmetric <code>true</code> to obtain a PKS key, or
* <code>false</code> for a symmetric key
* @return a tuple (slot of private key, slot of public key / zero for
* symmetric keys) packaged in a short
*/
public static final short generateKey(boolean keyIsAsymmetric) {
byte privKeyIndex = findFreeKeySlot();
byte pubKeyIndex = privKeyIndex;
if (privKeyIndex == INVALID_KEY)
return INVALID_KEY_PAIR;
if (keyIsAsymmetric) {
// Asymmetric key pair.
KeyPair newKP = new KeyPair(KeyPair.ALG_RSA_CRT,
KeyBuilder.LENGTH_RSA_2048);
newKP.genKeyPair();
keys[privKeyIndex] = newKP.getPrivate();
pubKeyIndex = findFreeKeySlot();
JCSystem.requestObjectDeletion();
if (pubKeyIndex == INVALID_KEY) {
keys[privKeyIndex] = null;
newKP.getPrivate().clearKey();
newKP.getPublic().clearKey();
return INVALID_KEY_PAIR;
}
keys[pubKeyIndex] = newKP.getPublic();
}
else {
// Symmetric key.
DESKey key = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES3_2KEY,
false);
short keySize = (short)(key.getSize() / 8);
random(randomMaterial, (short)0, keySize);
key.setKey(randomMaterial, (short)0);
keys[privKeyIndex] = key;
pubKeyIndex = INVALID_KEY;
}
return Util.makeShort(privKeyIndex, pubKeyIndex);
}
private static final RSAPublicKey loadPublicKey(byte[] buffer, short offset) {
RSAPublicKey pubKey = (RSAPublicKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false);
short readOffset = (short)(offset + 4);
short length = Util.getShort(buffer, offset);
pubKey.setExponent(buffer, readOffset, length); readOffset += length;
length = Util.getShort(buffer, (short)(offset + 2));
pubKey.setModulus(buffer, readOffset, length);
return pubKey;
}
private static final DESKey loadSymmetricKey(byte[] buffer, short offset) {
DESKey sksKey = (DESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_DES,
KeyBuilder.LENGTH_DES3_2KEY,
false);
sksKey.setKey(buffer, offset);
return sksKey;
}
private static final RSAPrivateCrtKey loadPrivateKey(byte[] buffer,
short offset) {
RSAPrivateCrtKey privKey = (RSAPrivateCrtKey)KeyBuilder.buildKey(
KeyBuilder.TYPE_RSA_CRT_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false);
short readOffset = (short)(offset + 10);
short length = Util.getShort(buffer, offset);
privKey.setP(buffer, readOffset, length); readOffset += length;
length = Util.getShort(buffer, (short)(offset + 2));
privKey.setQ(buffer, readOffset, length); readOffset += length;
length = Util.getShort(buffer, (short)(offset + 4));
privKey.setDP1(buffer, readOffset, length); readOffset += length;
length = Util.getShort(buffer, (short)(offset + 6));
privKey.setDQ1(buffer, readOffset, length); readOffset += length;
length = Util.getShort(buffer, (short)(offset + 8));
privKey.setPQ(buffer, readOffset, length); readOffset += length;
return privKey;
}
public static final byte loadKey(byte[] buffer, short offset) {
byte keyIndex = findFreeKeySlot();
if(keyIndex == -1) return keyIndex;
if(buffer[offset] == ASYMMETRIC_PRIVKEY) {
// RSA private key
keys[keyIndex] = loadPrivateKey(buffer, (short)(offset + 1));
} else if(buffer[offset] == ASYMMETRIC_PUBKEY) {
// RSA public key
keys[keyIndex] = loadPublicKey(buffer, (short)(offset + 1));
} else if(buffer[offset] == SYMMETRIC_KEY) {
// Symmetric encryption key
keys[keyIndex] = loadSymmetricKey(buffer, (short)(offset + 1));
} else {
return INVALID_KEY;
}
return keyIndex;
}
public static final void releaseKey(byte keyIndex) {
keys[keyIndex].clearKey();
keys[keyIndex] = null;
persistent[keyIndex] = false;
JCSystem.requestObjectDeletion();
}
/**
* Releases all non-persistent keys.
*/
public static final void releaseVolatileKeys() {
for (byte i = 0; i < persistent.length; i++) {
if (persistent[i] == false && keys[i] != null)
releaseKey(i);
}
}
public static final short getKeyLength(byte keyIndex) {
Key key = keys[keyIndex];
if (key instanceof RSAPrivateCrtKey) {
// Private key has 5 numbers half the public key's size =
// 5 * # of bytes / 2 plus 10 bytes for header (5 shorts)
return (short)((key.getSize() >> 4) * 5 + 10 + KEY_HEADER_BYTES);
}
else if (key instanceof RSAPublicKey) {
// Public key has (e, m) so it's # of bytes * 2 = # of bits / 4
// plus 4 bytes for header (2 numbers shorts)
return (short)((key.getSize() >> 2) + 4 + KEY_HEADER_BYTES);
}
else if (key instanceof DESKey) {
// DES keys are so much more straightforward...
return (short)((key.getSize() >> 3) + KEY_HEADER_BYTES);
}
else
return 0;
}
private static final short savePublicKey(RSAPublicKey key, byte[] buffer,
short offset) {
short writeOffset = (short)(offset + 4);
// The public exponent E -- e in OpenSSL
short expSize = key.getExponent(buffer, writeOffset);
writeOffset += expSize;
Util.setShort(buffer, offset, expSize);
// The public modulus N -- n in OpenSSL
short modSize = key.getModulus(buffer, writeOffset); writeOffset += modSize;
Util.setShort(buffer, (short)(offset + 2), modSize);
return (short)(writeOffset - offset);
}
private static final short savePrivateKey(RSAPrivateCrtKey key, byte[] buffer,
short offset) {
short writeOffset = (short)(offset + 10);
// P, Q are the secret prime factors (N = PQ)
// D is the private exponent
// Secret prime factor P
short pSize = key.getP(buffer, writeOffset); writeOffset += pSize;
Util.setShort(buffer, (short)(offset + 0), pSize);
// Secret prime factor Q
short qSize = key.getQ(buffer, writeOffset); writeOffset += qSize;
Util.setShort(buffer, (short)(offset + 2), qSize);
// D mod (P-1) -- dmp1 in OpenSSL
short dp1Size = key.getDP1(buffer, writeOffset); writeOffset += dp1Size;
Util.setShort(buffer, (short)(offset + 4), dp1Size);
// D mod (Q-1) -- dmq1 in OpenSSL
short dq1Size = key.getDQ1(buffer, writeOffset); writeOffset += dq1Size;
Util.setShort(buffer, (short)(offset + 6), dq1Size);
// Q^(-1) mod P -- iqmp in OpenSSL
short pqSize = key.getPQ(buffer, writeOffset); writeOffset += pqSize;
Util.setShort(buffer, (short)(offset + 8), pqSize);
return (short)(writeOffset - offset);
}
private static final short saveSymmetricKey(DESKey key, byte[] buffer,
short offset) {
return key.getKey(buffer, offset);
}
private static final byte getKeyType(Key key) {
if (key instanceof RSAPrivateCrtKey)
return ASYMMETRIC_PRIVKEY;
else if (key instanceof RSAPublicKey)
return ASYMMETRIC_PUBKEY;
else if (key instanceof DESKey)
return SYMMETRIC_KEY;
return INVALID_KEY;
}
public static final short saveKey(byte keyIndex, byte[] buffer,
short offset) {
short writeOffset = (short)(offset + KEY_HEADER_BYTES);
Key key = keys[keyIndex];
buffer[offset] = getKeyType(key);
if (key instanceof RSAPrivateCrtKey)
writeOffset = savePrivateKey((RSAPrivateCrtKey)key, buffer, writeOffset);
else if (key instanceof RSAPublicKey)
writeOffset = savePublicKey((RSAPublicKey)key, buffer, writeOffset);
else if (key instanceof DESKey)
writeOffset = saveSymmetricKey((DESKey)key, buffer, writeOffset);
return (short)(writeOffset + KEY_HEADER_BYTES);
}
/** Computes the maximum size of the result of encrypting some data.
*
* @param keyIndex the entry in the key file pointing to the encryption key
* @param plainBytes the number of bytes be encrypted
* @return the maximum size of the result of using the given key to encrypt
* plainBytes bytes of data
*/
public static final short getEncryptedDataSize(byte keyIndex,
short plainBytes) {
Key pk = keys[keyIndex];
if (pk instanceof DESKey) {
short blockSize = 8; // NOTE: don't know how to get block size from API
return (short)(((plainBytes + blockSize) / blockSize) * blockSize);
}
short outBlockSize = (short)(pk.getSize() >> 3);
short inBlockSize = (short)(outBlockSize - pksCipherPadding);
return (short)((plainBytes + inBlockSize - 1) / inBlockSize * outBlockSize);
}
/**
* Performs encryption / decryption.
*
* When using asymmetric keys, decryption should be performed using the pair
* of the key used for encryption.
*
* @param keyIndex the ID of the key to be used for *cryption
* @param sourceBuffer the buffer containing the data to be *crypted
* @param sourceOffset the offset of the first byte in sourceBuffer that
* contains the data to be *crypted
* @param sourceLength the number of bytes to be *crypted
* @param outBuffer the buffer that will receive the *crypted result
* @param outOffset the offset of the first byte in outBuffer that will
* receive the *crypted result
* @param doEncrypt <code>true</code> to indicate encryption, or
* <code>false</code> for decryption
* @return the number of bytes written to outBuffer
*/
public static final short cryptWithKey(byte keyIndex, byte[] sourceBuffer,
short sourceOffset, short sourceLength,
byte[] outBuffer, short outOffset,
boolean doEncrypt) {
Key cryptKey = keys[keyIndex];
Cipher cipher;
short inBlockSize;
if (cryptKey instanceof DESKey) {
// Prepare for symmetric encryption
cipher = symCipher;
inBlockSize = sourceLength;
}
else {
// Prepare for asymmetric encryption
cipher = asymCipher;
if (doEncrypt) {
inBlockSize = (short)((cryptKey.getSize() >> 3) - pksCipherPadding);
}
else {
inBlockSize = (short)(cryptKey.getSize() >> 3);
}
}
cipher.init(cryptKey,
doEncrypt ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT);
short stopOffset = (short)(sourceOffset + sourceLength);
short writeOffset = outOffset;
for(; sourceOffset < stopOffset; sourceOffset += inBlockSize) {
short blockSize = (stopOffset - sourceOffset >= inBlockSize) ? inBlockSize
: (short)(stopOffset - sourceOffset);
writeOffset += cipher.doFinal(sourceBuffer, sourceOffset, blockSize,
outBuffer, writeOffset);
}
return (short)(writeOffset - outOffset);
}
/**
* Performs a signature operaion (sign / verify).
*
* When using asymmetric keys, verification should be performed using the pair
* of the key used for signing.
*
* @param keyIndex the ID of the key to be used for signing / verification
* @param dataBuffer the buffer containing the data to be signed / that was
* signed
* @param dataOffset the offset of the first byte in dataBuffer
* that contains the data to be signed / that was signed
* @param dataLength the number of bytes to be signed / that were signed
* @param signBuffer the buffer that will receive the signature / contains the
* signature to be verified
* @param signOffset the offset of the first byte in outBuffer that will
* receive the signature / contains the signature to be
* verified
* @param doSign <code>true</code> to indicate signing, or <code>false</code>
* for signature verification
* @return the number of bytes written to signBuffer, when in signing mode;
* 0 (fail) or 1 (pass) in verification mode
*/
public static final short signWithKey(short keyIndex, byte[] dataBuffer,
short dataOffset, short dataLength,
byte[] signBuffer, short signOffset,
boolean doSign) {
Key signKey = keys[keyIndex];
Signature signature;
short signatureSize;
if (signKey instanceof DESKey) {
// HMAC setup
signature = symSignature;
signatureSize = 8; // NOTE: don't know how to get block size from API
}
else {
// PKS signing setup
signature = asymSignature;
signatureSize = (short)(signKey.getSize() >> 3);
}
signature.init(signKey,
doSign ? Signature.MODE_SIGN : Signature.MODE_VERIFY);
if (doSign) {
return signature.sign(dataBuffer, dataOffset, dataLength, signBuffer,
signOffset);
}
return (short)(signature.verify(dataBuffer, dataOffset, dataLength,
signBuffer, signOffset, signatureSize) ?
0x01 : 0x00);
}
/**
* Sets the authorization secret for a key.
*
* @param keyIndex the ID of the key
* @param buffer the buffer containing the authorization secret
* @param offset the offset of the first byte of the authorization secret in
* the given buffer
*/
public static final void setKeyAuth(byte keyIndex, byte[] buffer,
short offset) {
short authOffset = (short)(keyIndex * AUTH_SIZE);
Util.arrayCopyNonAtomic(buffer, offset, authorizations, authOffset,
AUTH_SIZE);
persistent[keyIndex] = true;
}
/**
* Verifies the authorization secret for a key.
*
* @param keyIndex the ID of the key
* @param buffer the buffer containing the authorization secret to be verified
* @param offset the offset of the first byte of the authorization secret in
* the given buffer
* @return <code>true</code> if the given secret matches the key's
* authorization secret, <code>false</code> otherwise
*/
public static final boolean verifyKeyAuth(byte keyIndex, byte[] buffer,
short offset) {
if (persistent[keyIndex] == false)
return false;
short authOffset = (short)(keyIndex * AUTH_SIZE);
return Util.arrayCompare(buffer, offset, authorizations, authOffset,
AUTH_SIZE) == 0;
}
public static final short digest(byte[] sourceBuffer, short sourceOffset,
short sourceLength, byte[] outBuffer,
short outOffset) {
digest.reset();
return digest.doFinal(sourceBuffer, sourceOffset, sourceLength, outBuffer,
outOffset);
}
public static final short digest2(byte[] sourceBuffer1, short sourceOffset1,
short sourceLength1, byte[] sourceBuffer2,
short sourceOffset2, short sourceLength2,
byte[] outBuffer, short outOffset) {
digest.reset();
digest.update(sourceBuffer1, sourceOffset1, sourceLength1);
return digest.doFinal(sourceBuffer2, sourceOffset2, sourceLength2,
outBuffer, outOffset);
}
public static final short getDigestLength() {
return digest.getLength();
}
public static final short getDigestBlockLength() {
// NOTE: Don't khow to get message digest block lengths in the JavaCard API.
return (short)20;
}
public static final void random(byte[] buffer, short offset, short length) {
randomizer.generateData(buffer, offset, length);
}
/**
* Dumps the state of the TEM cryptographic engine.
*
* @param output the buffer that will receive the stat results
* @param outputOffset the offset of the first byte in the output buffer that
* will receive the stat results
* @return the number of bytes written to the output buffer
*/
public static final short stat(byte[] output, short outputOffset) {
short o = outputOffset;
// status for each key
for (byte i = 0; i < NUM_KEYS; i++) {
if (keys[i] == null)
continue;
output[o] = i; o++;
output[o] = getKeyType(keys[i]); o++;
Util.setShort(output, o, keys[i].getSize()); o += 2;
}
return (short)(o - outputOffset);
}
}