diff --git a/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCipherImpl.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCipherImpl.java new file mode 100644 index 00000000..ec34fd32 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCipherImpl.java @@ -0,0 +1,42 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.keymaster.KMCipher; +import javacardx.crypto.Cipher; + +public class KMCipherImpl extends KMCipher{ + Cipher cipher; + short cipherAlg; + short paddingAlg; + KMCipherImpl(Cipher c){ + cipher = c; + } + + @Override + public short doFinal(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { + return cipher.doFinal(buffer, startOff, length, scratchPad, i); + } + + @Override + public short getCipherAlgorithm() { + return cipherAlg; + } + + @Override + public void setCipherAlgorithm(short alg) { + cipherAlg = alg; + } + @Override + public short update(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { + return cipher.update(buffer,startOff,length,scratchPad,i); + } + + @Override + public short getPaddingAlgorithm() { + return paddingAlg; + } + + @Override + public void setPaddingAlgorithm(short alg) { + paddingAlg = alg; + } +} diff --git a/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java new file mode 100644 index 00000000..917b4911 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java @@ -0,0 +1,8 @@ + +package com.android.javacard.keymaster; + +public class KMCryptoProviderImpl { + public static KMCryptoProvider instance(){ + return new KMJcardSimulator(); + } +} diff --git a/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMJcardSimulator.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMJcardSimulator.java new file mode 100644 index 00000000..3a4a57b5 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMJcardSimulator.java @@ -0,0 +1,781 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.RSAPublicKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.Cipher; +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.ShortBufferException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * Simulator only supports 512 bit RSA key pair, 128 AES Key, 128 bit 3Des key, less then 256 bit EC + * Key, and upto 512 bit HMAC key. Also simulator does not support TRNG, so this implementation just + * creates its own RNG using PRNG. + */ +public class KMJcardSimulator implements KMCryptoProvider { + public static final short AES_GCM_TAG_LENGTH = 12; + public static final short AES_GCM_NONCE_LENGTH = 12; + public static final short MAX_RND_NUM_SIZE = 64; + public static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys + public static final byte[] aesICV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + private static final int AES_GCM_KEY_SIZE = 16; + public static boolean jcardSim = false; + private static Signature kdf; + private static Signature hmacSignature; + + private static byte[] rngCounter; + private static AESKey aesRngKey; + private static Cipher aesRngCipher; + private static byte[] entropyPool; + private static byte[] rndNum; + + // Implements Oracle Simulator based restricted crypto provider + public KMJcardSimulator() { + // Various Keys + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + // RNG + rndNum = JCSystem.makeTransientByteArray(MAX_RND_NUM_SIZE, JCSystem.CLEAR_ON_RESET); + entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); + rngCounter = JCSystem.makeTransientByteArray((short) 8, JCSystem.CLEAR_ON_RESET); + initEntropyPool(entropyPool); + try { + aesRngCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + } catch (CryptoException exp) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + aesRngKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + // various ciphers + + } + + @Override + public KeyPair createRsaKeyPair() { + KeyPair rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + rsaKeyPair.genKeyPair(); + return rsaKeyPair; + } + + @Override + public RSAPrivateKey createRsaKey(byte[] modBuffer, short modOff, short modLength, + byte[] privBuffer, short privOff, short privLength) { + KeyPair rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + + } + + @Override + public KeyPair createECKeyPair() { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + ecKeyPair.genKeyPair(); + return ecKeyPair; + } + + @Override + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + privKey.setS(privBuffer,privOff, privLength); + return privKey; + } + + @Override + public AESKey createAESKey(short keysize) { + byte[] rndNum = new byte[(short) (keysize/8)]; + return createAESKey(rndNum, (short)0, (short)rndNum.length); + } + + @Override + public AESKey createAESKey(byte[] buf, short startOff, short length) { + AESKey key = null; + short keysize = (short)(length * 8); + if (keysize == 128) { + key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + key.setKey(buf, (short) startOff); + }else if (keysize == 256){ + key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); + key.setKey(buf, (short) startOff); + } + // byte[] buffer = new byte[length]; + // Util.arrayCopyNonAtomic(buf, startOff, buffer, (short)0,length); + // print("AES Key", buffer); + return key; + } + + @Override + public DESKey createTDESKey() { + // TODO check whether 168 bit or 192 bit + byte[] rndNum = new byte[24]; + newRandomNumber(rndNum, (short) 0, (short)rndNum.length); + return createTDESKey(rndNum, (short)0, (short)rndNum.length); + } + + @Override + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + DESKey triDesKey = + (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false); + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + @Override + public HMACKey createHMACKey(short keysize) { + if((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + byte[] rndNum = new byte[(short) (keysize/8)]; + newRandomNumber(rndNum, (short) 0, (short)(keysize/8)); + return createHMACKey(rndNum, (short)0, (short)rndNum.length); + } + + @Override + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + HMACKey key = null; + key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, + KeyBuilder.LENGTH_HMAC_SHA_256_BLOCK_64, false); + key.setKey(secretBuffer,secretOff,secretLength); + return key; + } + + @Override + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + //Create the sun jce compliant aes key + if(key.getSize() != 128){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + byte[] keyMaterial = new byte[16]; + key.getKey(keyMaterial,(short)0); + //print("KeyMaterial Enc", keyMaterial); + //print("Authdata Enc", authData, authDataStart, authDataLen); + java.security.Key aesKey = new SecretKeySpec(keyMaterial,(short)0,(short)16, "AES"); + // Create the cipher + javax.crypto.Cipher cipher = null; + try { + cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } catch (NoSuchProviderException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // Copy nonce + if(nonceLen != AES_GCM_NONCE_LENGTH){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; + Util.arrayCopyNonAtomic(nonce,nonceStart,iv,(short)0,AES_GCM_NONCE_LENGTH); + // Init Cipher + GCMParameterSpec spec = new GCMParameterSpec(AES_GCM_TAG_LENGTH * 8, nonce,nonceStart,AES_GCM_NONCE_LENGTH); + try { + cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, spec); + } catch (InvalidKeyException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + // Create auth data + byte[] aad = new byte[authDataLen]; + Util.arrayCopyNonAtomic(authData,authDataStart,aad,(short)0,authDataLen); + // print("AAD", aad); + cipher.updateAAD(aad); + // Encrypt secret + short len = 0; + byte[] outputBuf = new byte[cipher.getOutputSize(secretLen)]; + try { + len = (short)(cipher.doFinal(secret,secretStart,secretLen,outputBuf,(short)0)); + } catch (ShortBufferException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (BadPaddingException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // Extract Tag appended at the end. + Util.arrayCopyNonAtomic(outputBuf, (short)(len - AES_GCM_TAG_LENGTH),authTag,authTagStart,AES_GCM_TAG_LENGTH); + //Copy the encrypted data + Util.arrayCopyNonAtomic(outputBuf, (short)0,encSecret,encSecretStart,(short)(len - AES_GCM_TAG_LENGTH)); + return (short)(len - AES_GCM_TAG_LENGTH); + } + +/* + // Decrypt; nonce is shared implicitly + cipher.init(Cipher.DECRYPT_MODE, key, spec); + + // EXPECTED: Uncommenting this will cause an AEADBadTagException when decrypting + // because AAD value is altered + if (testNum == 1) aad[1]++; + + cipher.updateAAD(aad); + + // EXPECTED: Uncommenting this will cause an AEADBadTagException when decrypting + // because the encrypted data has been altered + if (testNum == 2) cipherText[10]++; + + // EXPECTED: Uncommenting this will cause an AEADBadTagException when decrypting + // because the tag has been altered + if (testNum == 3) cipherText[cipherText.length - 2]++; + + try { + byte[] plainText = cipher.doFinal(cipherText); + if (testNum != 0) { + System.out.println("Test Failed: expected AEADBadTagException not thrown"); + } else { + // check if the decryption result matches + if (Arrays.equals(input, plainText)) { + System.out.println("Test Passed: match!"); + } else { + System.out.println("Test Failed: result mismatch!"); + System.out.println(new String(plainText)); + } + } + } catch(AEADBadTagException ex) { + if (testNum == 0) { + System.out.println("Test Failed: unexpected ex " + ex); + ex.printStackTrace(); + } else { + System.out.println("Test Passed: expected ex " + ex); + } + } + } + }*/ + + public boolean aesGCMDecrypt( + AESKey key, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + //Create the sun jce compliant aes key + if(key.getSize() != 128){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + byte[] keyMaterial = new byte[16]; + key.getKey(keyMaterial,(short)0); + //print("KeyMaterial Dec", keyMaterial); + //print("Authdata Dec", authData, authDataStart, authDataLen); + + java.security.Key aesKey = new SecretKeySpec(keyMaterial,(short)0,(short)16, "AES"); + // Create the cipher + javax.crypto.Cipher cipher = null; + try { + cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } catch (NoSuchProviderException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // Copy nonce + if(nonceLen != AES_GCM_NONCE_LENGTH){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; + Util.arrayCopyNonAtomic(nonce,nonceStart,iv,(short)0,AES_GCM_NONCE_LENGTH); + // Init Cipher + GCMParameterSpec spec = new GCMParameterSpec(AES_GCM_TAG_LENGTH * 8, nonce,nonceStart,AES_GCM_NONCE_LENGTH); + try { + cipher.init(javax.crypto.Cipher.DECRYPT_MODE, aesKey, spec); + } catch (InvalidKeyException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + // Create auth data + byte[] aad = new byte[authDataLen]; + Util.arrayCopyNonAtomic(authData,authDataStart,aad,(short)0,authDataLen); + cipher.updateAAD(aad); + // Append the auth tag at the end of data + byte[] inputBuf = new byte[(short)(encSecretLen + AES_GCM_TAG_LENGTH)]; + Util.arrayCopyNonAtomic(encSecret,encSecretStart,inputBuf,(short)0,encSecretLen); + Util.arrayCopyNonAtomic(authTag,authTagStart,inputBuf,encSecretLen,AES_GCM_TAG_LENGTH); + // Decrypt + short len = 0; + byte[] outputBuf = new byte[cipher.getOutputSize((short)inputBuf.length)]; + try { + len = (short)(cipher.doFinal(inputBuf,(short)0,(short)inputBuf.length,outputBuf,(short)0)); + }catch(AEADBadTagException e){ + e.printStackTrace(); + return false; + }catch (ShortBufferException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (IllegalBlockSizeException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (BadPaddingException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + //Copy the decrypted data + Util.arrayCopyNonAtomic(outputBuf, (short)0,secret,secretStart,len); + return true; + } + + @Override + public byte[] getTrueRandomNumber(short i) { + // ignore the size as simulator only supports 128 bit entropy + return entropyPool; + } + + @Override + public short aesCCMSign( + byte[] bufIn, + short bufInStart, + short buffInLength, + byte[] masterKeySecret, + byte[] bufOut, + short bufStart) { + if (masterKeySecret.length > 16) { + return -1; + } + + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + key.setKey(masterKeySecret, (short) 0); + byte[] in = new byte[buffInLength]; + Util.arrayCopyNonAtomic(bufIn, bufInStart,in,(short)0,buffInLength); + kdf.init(key, Signature.MODE_SIGN); + short len = kdf.sign(bufIn, bufInStart, buffInLength, bufOut, bufStart); + byte[] out = new byte[len]; + Util.arrayCopyNonAtomic(bufOut, bufStart,out,(short)0,len); + return len; + } + + + @Override + public HMACKey cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength) { + // This is hardcoded to requirement - 32 byte output with two concatenated 16 bytes K1 and K2. + final byte n = 2; // hardcoded - L is 32 bytes and h is 16 byte i.e. CMAC output of AES 128 bit key. + final byte[] L = {0,0,1,0}; // [L] 256 bits - hardcoded 32 bits as per reference impl in keymaster. + final byte[] zero = {0}; // + byte[] iBuf = new byte[]{0,0,0,0}; // [i] counter - 32 bits + byte[] keyOut = new byte[(short)(n*16)]; + Signature prf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + key.setKey(keyMaterial, (short) 0); + prf.init(key, Signature.MODE_SIGN); + byte i =1; + short pos = 0; + while (i <= n) { + iBuf[3] = i; + prf.update(iBuf, (short) 0, (short) 4); // 4 bytes of iBuf with counter in it + prf.update(label, (short) 0, (short) label.length); // label + prf.update(zero, (short) 0, (short) 1); // 1 byte of 0x00 + prf.update(context, contextLength, contextLength); // context + pos = prf.sign(L, (short) 0, (short) 4, keyOut, pos); // 4 bytes of L - signature of 16 bytes + i++; + } + return createHMACKey(keyOut, (short)0, (short)keyOut.length); + } + + @Override + public short hmacSign(HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + hmacSignature.init(key, Signature.MODE_SIGN); + return hmacSignature.sign(data, dataStart, dataLength, mac, macStart); + } + + @Override + public boolean hmacVerify(HMACKey key, byte[] data, short dataStart, short dataLength, + byte[] mac, short macStart, short macLength) { + hmacSignature.init(key, Signature.MODE_VERIFY); + return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength); + } + + @Override + public KMCipher createRsaDecipher(short padding, byte[] secret, short secretStart, + short secretLength, byte[] modBuffer, short modOff, short modLength) { + byte cipherAlg = Cipher.ALG_RSA_NOPAD; + //TODO implement OAEP algorithm using SunJCE. + if(padding == KMCipher.PAD_PKCS1_OAEP_SHA256) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_PKCS1) cipherAlg = Cipher.ALG_RSA_PKCS1; + else cipherAlg = Cipher.ALG_RSA_NOPAD; + Cipher rsaCipher = Cipher.getInstance(cipherAlg,false); + RSAPrivateKey key = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false); + key.setExponent(secret,secretStart,secretLength); + key.setModulus(modBuffer, modOff, modLength); + rsaCipher.init(key,Cipher.MODE_DECRYPT); + return new KMCipherImpl(rsaCipher); + } + + @Override + public Signature createRsaSigner(short msgDigestAlg, short padding, byte[] secret, short secretStart, short secretLength, byte[] modBuffer, short modOff, short modLength) { + short alg = Signature.ALG_RSA_SHA_256_PKCS1; + if(msgDigestAlg == MessageDigest.ALG_NULL) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_NOPAD) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_PKCS1_PSS) alg = Signature.ALG_RSA_SHA_256_PKCS1_PSS; + else if(padding == KMCipher.PAD_PKCS1) alg = Signature.ALG_RSA_SHA_256_PKCS1; + else CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + Signature rsaSigner = Signature.getInstance((byte)alg, false); + RSAPrivateKey key = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false); + key.setExponent(secret,secretStart,secretLength); + key.setModulus(modBuffer, modOff, modLength); + rsaSigner.init(key,Signature.MODE_SIGN); + return rsaSigner; + } + + @Override + public Signature createEcSigner(short msgDigestAlg, byte[] secret, short secretStart, short secretLength) { + short alg = Signature.ALG_ECDSA_SHA_256; + if(msgDigestAlg == MessageDigest.ALG_NULL) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + //KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + //ecKeyPair.genKeyPair(); + //ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + //privKey.setS(secret,secretStart, secretLength); + ECPrivateKey key = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + key.setS(secret,secretStart,secretLength); + Signature ecSigner = Signature.getInstance((byte)alg,false); + ecSigner.init(key,Signature.MODE_SIGN); + return ecSigner; + } + + @Override + public KMCipher createSymmetricCipher( + short cipherAlg, short mode, short padding, byte[] secret, short secretStart, short secretLength) { + return createSymmetricCipher(cipherAlg, mode, padding, secret,secretStart,secretLength,null,(short)0,(short)0); + } + + @Override + public KMCipher createSymmetricCipher(short cipherAlg, short mode, short padding, byte[] secret, + short secretStart, short secretLength, + byte[] ivBuffer, short ivStart, short ivLength) { + Key key = null; + Cipher symmCipher = null; + short len = 0; + switch (secretLength){ + case 32: + len = KeyBuilder.LENGTH_AES_256; + break; + case 16: + len = KeyBuilder.LENGTH_AES_128; + break; + case 24: + len = KeyBuilder.LENGTH_DES3_3KEY; + break; + default: + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + break; + } + switch(cipherAlg){ + case KMCipher.ALG_AES_BLOCK_128_CBC_NOPAD: + cipherAlg = Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; + key = KeyBuilder.buildKey(KeyBuilder.TYPE_AES,len,false); + ((AESKey) key).setKey(secret,secretStart); + symmCipher = Cipher.getInstance((byte)cipherAlg, false); + symmCipher.init(key, (byte) mode, ivBuffer, ivStart, ivLength); + break; + case KMCipher.ALG_AES_BLOCK_128_ECB_NOPAD: + cipherAlg = Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; + key = KeyBuilder.buildKey(KeyBuilder.TYPE_AES,len,false); + ((AESKey) key).setKey(secret,secretStart); + symmCipher = Cipher.getInstance((byte)cipherAlg, false); + symmCipher.init(key, (byte) mode); + break; + case KMCipher.ALG_DES_CBC_NOPAD: + cipherAlg = Cipher.ALG_DES_CBC_NOPAD; + key = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,len,false); + ((DESKey) key).setKey(secret,secretStart); + symmCipher = Cipher.getInstance((byte)cipherAlg, false); + symmCipher.init(key, (byte) mode, ivBuffer, ivStart, ivLength); + break; + case KMCipher.ALG_DES_ECB_NOPAD: + cipherAlg = Cipher.ALG_DES_ECB_NOPAD; + key = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES,len,false); + ((DESKey) key).setKey(secret,secretStart); + symmCipher = Cipher.getInstance((byte)cipherAlg, false); + symmCipher.init(key, (byte) mode); + break; + default://This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + KMCipher cipher = new KMCipherImpl(symmCipher); + cipher.setCipherAlgorithm(cipherAlg); + cipher.setPaddingAlgorithm(padding); + return cipher; + } + + @Override + public Signature createHmacSignerVerifier(short purpose, short msgDigestAlg, byte[] secret, short secretStart, short secretLength) { + short alg = Signature.ALG_HMAC_SHA_256; + if(msgDigestAlg != MessageDigest.ALG_SHA_256) CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + Signature hmacSignerVerifier = Signature.getInstance((byte)alg,false); + HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short)(secretLength*8), false); + key.setKey(secret,secretStart,secretLength); + hmacSignerVerifier.init(key,(byte)purpose); + return hmacSignerVerifier; + } + + @Override + public KMCipher createGCMCipher(short mode, byte[] secret, short secretStart, short secretLength, byte[] ivBuffer, short ivStart, short ivLength) { + //TODO + short len = KeyBuilder.LENGTH_AES_128; + if(secretLength == 32){ + len = KeyBuilder.LENGTH_AES_256; + } + return new KMCipherImpl(null); + } + + @Override + public void delete(KMCipher cipher) { + //Don't do anything as we don't pool the objects. + } + + @Override + public void delete(Signature signature) { + //Don't do anything as we don't pool the objects. + } + + @Override + public void delete(Key key) { + // Don't do anything as we don't pool the objects. + } + + @Override + public void delete(KeyPair keyPair) { + // Don't do anything as we don't pool the objects. + } + + private void initEntropyPool(byte[] pool) { + byte index = 0; + RandomData trng; + while (index < rngCounter.length) { + rngCounter[index++] = 0; + } + try { + trng = RandomData.getInstance(RandomData.ALG_TRNG); + trng.nextBytes(pool, (short) 0, (short) pool.length); + } catch (CryptoException exp) { + if (exp.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + // TODO change this when possible + // simulator does not support TRNG algorithm. So, PRNG algorithm (deprecated) is used. + trng = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); + trng.nextBytes(pool, (short) 0, (short) pool.length); + } else { + // TODO change this to proper error code + ISOException.throwIt(ISO7816.SW_UNKNOWN); + } + } + } + + // Generate a secure random number from existing entropy pool. This uses aes ecb algorithm with + // 8 byte rngCounter and 16 byte block size. + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + KMRepository repository = KMRepository.instance(); + byte[] bufPtr = repository.getHeap(); + short countBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short randBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short len = KMKeymasterApplet.AES_BLOCK_SIZE; + aesRngKey.setKey(entropyPool, (short) 0); + aesRngCipher.init(aesRngKey, Cipher.MODE_ENCRYPT, aesICV, (short) 0, (short) 16); + while (length > 0) { + if (length < len) len = length; + // increment rngCounter by one + incrementCounter(); + // copy the 8 byte rngCounter into the 16 byte rngCounter buffer. + Util.arrayCopy(rngCounter, (short) 0, bufPtr, countBufInd, (short) rngCounter.length); + // encrypt the rngCounter buffer with existing entropy which forms the aes key. + aesRngCipher.doFinal( + bufPtr, countBufInd, KMKeymasterApplet.AES_BLOCK_SIZE, bufPtr, randBufInd); + // copy the encrypted rngCounter block to buffer passed in the argument + Util.arrayCopy(bufPtr, randBufInd, num, startOff, len); + length = (short) (length - len); + startOff = (short) (startOff + len); + } + } + + // increment 8 byte rngCounter by one + private void incrementCounter() { + // start with least significant byte + short index = (short) (rngCounter.length - 1); + while (index >= 0) { + // if the msb of current byte is set then it will be negative + if (rngCounter[index] < 0) { + // then increment the rngCounter + rngCounter[index]++; + // is the msb still set? i.e. no carry over + if (rngCounter[index] < 0) break; // then break + else index--; // else go to the higher order byte + } else { + // if msb is not set then increment the rngCounter + rngCounter[index]++; + break; + } + } + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + // Maximum length can be 256 bytes. But currently we support max 32 bytes seed. + // Get existing entropy pool. + if (length > 32) length = 32; + // Create new temporary pool. + // Populate the new pool with the entropy which is derived from current entropy pool. + newRandomNumber(rndNum, (short) 0, (short) entropyPool.length); + // Copy the entropy to the current pool - updates the entropy pool. + Util.arrayCopy(rndNum, (short) 0, entropyPool, (short) 0, (short) entropyPool.length); + short index = 0; + short randIndex = 0; + // XOR the seed received from the master in the entropy pool - 16 bytes (entPool.length). + // at a time. + while (index < length) { + entropyPool[randIndex] = (byte) (entropyPool[randIndex] ^ num[(short) (offset + index)]); + randIndex++; + index++; + if (randIndex >= entropyPool.length) { + randIndex = 0; + } + } + } + + @Override + public void bypassAesGcm(){ + //ignore + } + + @Override + public KMCipher createRsaCipher(short padding, byte[] modBuffer, short modOff, short modLength) { + byte cipherAlg = Cipher.ALG_RSA_NOPAD; + //TODO implement OAEP algorithm using SunJCE. + //TODO for no pad the buffer length must be 255 max. + if(padding == KMCipher.PAD_PKCS1_OAEP_SHA256) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_PKCS1) cipherAlg = Cipher.ALG_RSA_PKCS1; + else cipherAlg = Cipher.ALG_RSA_NOPAD; + Cipher rsaCipher = Cipher.getInstance(cipherAlg,false); + RSAPublicKey key = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false); + byte[] exponent = new byte[]{0x01,0x00,0x01}; + key.setExponent(exponent,(short)0,(short)3); + key.setModulus(modBuffer, modOff, modLength); + rsaCipher.init(key,Cipher.MODE_ENCRYPT); + return new KMCipherImpl(rsaCipher); + } + + @Override + public Signature createRsaVerifier(short msgDigestAlg, short padding, byte[] modBuffer, short modOff, short modLength) { + short alg = Signature.ALG_RSA_SHA_256_PKCS1; + if(msgDigestAlg == MessageDigest.ALG_NULL) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_NOPAD) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + else if(padding == KMCipher.PAD_PKCS1_PSS) alg = Signature.ALG_RSA_SHA_256_PKCS1_PSS; + else if(padding == KMCipher.PAD_PKCS1) alg = Signature.ALG_RSA_SHA_256_PKCS1; + else CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + Signature rsaVerifier = Signature.getInstance((byte)alg, false); + RSAPublicKey key = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_2048, false); + byte[] exponent = new byte[]{0x01,0x00,0x01}; + key.setExponent(exponent,(short)0,(short)3); + key.setModulus(modBuffer, modOff, modLength); + rsaVerifier.init(key,Signature.MODE_VERIFY); + return rsaVerifier; + } + @Override + public Signature createEcVerifier(short msgDigestAlg, byte[] pubKey, short pubKeyStart, short pubKeyLength) { + short alg = Signature.ALG_ECDSA_SHA_256; + if(msgDigestAlg == MessageDigest.ALG_NULL) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); +// KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); +// ecKeyPair.genKeyPair(); +// ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic(); + ECPublicKey key = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, KeyBuilder.LENGTH_EC_FP_256, false); + key.setW(pubKey,pubKeyStart,pubKeyLength); + Signature ecVerifier = Signature.getInstance((byte)alg,false); + ecVerifier.init(key,Signature.MODE_VERIFY); + return ecVerifier; + } + /* + private static void print (String lab, byte[] b, short s, short l){ + byte[] i = new byte[l]; + Util.arrayCopyNonAtomic(b,s,i,(short)0,l); + print(lab,i); + } + private static void print(String label, byte[] buf){ + System.out.println(label+": "); + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < buf.length; i++){ + sb.append(String.format(" 0x%02X", buf[i])) ; + if(((i-1)%38 == 0) && ((i-1) >0)){ + sb.append(";\n"); + } + } + System.out.println(sb.toString()); + }*/ +} diff --git a/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCipherImpl.java b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCipherImpl.java new file mode 100644 index 00000000..ccc1dc7e --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCipherImpl.java @@ -0,0 +1,44 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.keymaster.KMCipher; +import javacardx.crypto.Cipher; + +public class KMCipherImpl extends KMCipher{ + Cipher cipher; + short cipherAlg; + short paddingAlg; + KMCipherImpl(Cipher c){ + cipher = c; + } + + @Override + public short doFinal(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { + return cipher.doFinal(buffer, startOff, length, scratchPad, i); + } + + @Override + public short getCipherAlgorithm() { + return cipher.getCipherAlgorithm(); + } + + + @Override + public void setPaddingAlgorithm(short alg) { + paddingAlg = alg; + } + + @Override + public void setCipherAlgorithm(short alg) { + cipherAlg = alg; + } + + @Override + public short update(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { + return cipher.update(buffer,startOff,length,scratchPad,i); + } + + @Override + public short getPaddingAlgorithm() { + return cipher.getPaddingAlgorithm(); + } +} diff --git a/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java new file mode 100644 index 00000000..182dda4e --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCryptoProviderImpl.java @@ -0,0 +1,8 @@ + +package com.android.javacard.keymaster; + +public class KMCryptoProviderImpl { + public static KMCryptoProvider instance(){ + return new KMSimulator(); + } +} diff --git a/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSimulator.java b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSimulator.java new file mode 100644 index 00000000..12fb7699 --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSimulator.java @@ -0,0 +1,524 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.RSAPrivateKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * Simulator only supports 512 bit RSA key pair, 128 AES Key, 128 bit 3Des key, less then 256 bit EC + * Key, and upto 512 bit HMAC key. Also simulator does not support TRNG, so this implementation just + * creates its own RNG using PRNG. + */ +public class KMSimulator implements KMCryptoProvider { + public static final short AES_GCM_TAG_LENGTH = 12; + public static final short AES_GCM_NONCE_LENGTH = 12; + public static final short MAX_RND_NUM_SIZE = 64; + public static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys + public static final byte[] aesICV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + public static boolean jcardSim = false; + + private static KeyPair rsa512KeyPair; + private static KeyPair ec192KeyPair; + private static AESKey aes128Key; + private static DESKey triDesKey; + private static HMACKey hmac128Key; + private static HMACKey hmac256Key; + private static AEADCipher aesGcmCipher; + private static AESKey derivedKey; + private static Signature kdf; + + private static byte[] rngCounter; + private static AESKey aesRngKey; + private static Cipher aesRngCipher; + private static byte[] entropyPool; + private static byte[] rndNum; + + // Implements Oracle Simulator based restricted crypto provider + public KMSimulator() { + // Various Keys + rsa512KeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_512); + ec192KeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_192); + aes128Key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + triDesKey = + (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_2KEY, false); + hmac128Key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) 128, false); + hmac256Key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) 256, false); + derivedKey = + (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + + // RNG + rndNum = JCSystem.makeTransientByteArray(MAX_RND_NUM_SIZE, JCSystem.CLEAR_ON_RESET); + entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); + rngCounter = JCSystem.makeTransientByteArray((short) 8, JCSystem.CLEAR_ON_RESET); + initEntropyPool(entropyPool); + try { + aesRngCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); + } catch (CryptoException exp) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + aesRngKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + } + + @Override + public KeyPair createRsaKeyPair() { + // By default 65537 is used as public exponent no need to set the public exponent. Now generate + // 512 bit RSA keypair for the given exponent + rsa512KeyPair.genKeyPair(); + return rsa512KeyPair; + } + + @Override + public KeyPair createECKeyPair() { + // Simulator does not support 256 bit keys. + // Generate default 192 bit key pair supported by simulator. + ec192KeyPair.genKeyPair(); + return ec192KeyPair; + } + + @Override + public AESKey createAESKey(short keysize) { + // keysize is ignored as simulator only supports 128 bit aes key + newRandomNumber(rndNum, (short) 0, (short) 16); + aes128Key.setKey(rndNum, (short) 0); + return aes128Key; + } + + @Override + public AESKey createAESKey(byte[] buf, short startOff, short length) { + if (length > 16) length = 16; + else if(length < 16) return null; + aes128Key.setKey(buf, startOff); + return aes128Key; + } + + @Override + public DESKey createTDESKey() { + // only 128 bit keys are supported + newRandomNumber(rndNum, (short) 0, (short) 21); + triDesKey.setKey(rndNum, (short) 0); + return triDesKey; + } + + @Override + public HMACKey createHMACKey(short keysize) { + // simulator only supports HMAC keys for SHA1 and SHA256 with block size 64. + // So only 128 and 256 bit HMAC keys are supported. + HMACKey key; + if (keysize == 128) { + key = hmac128Key; + keysize = 16; + } else if (keysize == 256) { + key = hmac256Key; + keysize = 32; + } else { + key = hmac128Key; // by default the simulator will return 128 bit keys for SHA1 + keysize = 16; + } + newRandomNumber(rndNum, (short) 0, keysize); + key.setKey(rndNum, (short) 0, keysize); + return key; + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + // Maximum length can be 256 bytes. But currently we support max 32 bytes seed. + // Get existing entropy pool. + if (length > 32) length = 32; + // Create new temporary pool. + // Populate the new pool with the entropy which is derived from current entropy pool. + newRandomNumber(rndNum, (short) 0, (short) entropyPool.length); + // Copy the entropy to the current pool - updates the entropy pool. + Util.arrayCopy(rndNum, (short) 0, entropyPool, (short) 0, (short) entropyPool.length); + short index = 0; + short randIndex = 0; + // XOR the seed received from the master in the entropy pool - 16 bytes (entPool.length). + // at a time. + while (index < length) { + entropyPool[randIndex] = (byte) (entropyPool[randIndex] ^ num[(short) (offset + index)]); + randIndex++; + index++; + if (randIndex >= entropyPool.length) { + randIndex = 0; + } + } + } + + @Override + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (jcardSim) return -1; + if(authTagLen != AES_GCM_TAG_LENGTH){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if(nonceLen != AES_GCM_NONCE_LENGTH){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + byte[] aad = JCSystem.makeTransientByteArray(authDataLen, JCSystem.CLEAR_ON_RESET); + Util.arrayCopyNonAtomic(authData, authDataStart, aad, (short) 0, authDataLen); + try { + aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen); + } catch (CryptoException exp) { + KMException.throwIt(exp.getReason()); + } + // add the auth data + try { + aesGcmCipher.updateAAD(aad, (short) 0, authDataLen); + } catch (CryptoException exp) { + KMException.throwIt(exp.getReason()); + } + // encrypt the secret + short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart); + // The tag buffer must be exact size otherwise simulator returns 0 tag. + byte[] tag = JCSystem.makeTransientByteArray(AES_GCM_TAG_LENGTH, JCSystem.CLEAR_ON_RESET); + aesGcmCipher.retrieveTag(tag, (short) 0, AES_GCM_TAG_LENGTH); + Util.arrayCopyNonAtomic(tag,(short)0, authTag, authTagStart,AES_GCM_TAG_LENGTH); + return ciphLen; +/* aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + try { + aesGcmCipher.updateAAD(aad, (short) 0, authDataLen); + } catch (CryptoException exp) { + KMException.throwIt(exp.getReason()); + } + byte[] plain = JCSystem.makeTransientByteArray(secretLen, JCSystem.CLEAR_ON_RESET); + // encrypt the secret + ciphLen = aesGcmCipher.doFinal(encSecret, encSecretStart, ciphLen, plain, (short) 0); + boolean ver = aesGcmCipher.verifyTag(tag, (short) 0, (short) 12, (short) 12); + if (ver == true) { + KMException.throwIt((short) 10); + } else { + KMException.throwIt((short) 20); + } + return 0; + */ + } + + public boolean aesGCMDecrypt( + AESKey key, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if(jcardSim) return true; + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + // allocate aad buffer of exact size - otherwise simulator throws exception + byte[] aad = JCSystem.makeTransientByteArray(authDataLen, JCSystem.CLEAR_ON_RESET); + Util.arrayCopyNonAtomic(authData, authDataStart, aad, (short) 0, authDataLen); + // allocate tag of exact size. + byte[] tag = JCSystem.makeTransientByteArray(AES_GCM_TAG_LENGTH, JCSystem.CLEAR_ON_RESET); + Util.arrayCopyNonAtomic(authTag, authTagStart, tag, (short) 0, authTagLen); + boolean verification = false; + try { + aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + aesGcmCipher.updateAAD(aad, (short) 0, authDataLen); + //byte[] plain = JCSystem.makeTransientByteArray(encSecretLen, JCSystem.CLEAR_ON_RESET); + // encrypt the secret + aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart); + verification = aesGcmCipher.verifyTag(tag, (short) 0, (short) 12, (short) 12); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return verification; + } + + @Override + public byte[] getTrueRandomNumber(short i) { + // ignore the size as simulator only supports 128 bit entropy + return entropyPool; + } + + @Override + public short aesCCMSign( + byte[] bufIn, + short bufInStart, + short buffInLength, + byte[] masterKeySecret, + byte[] bufOut, + short bufStart) { + if (masterKeySecret.length > 16) { + + return -1; + } + aes128Key.setKey(masterKeySecret, (short) 0); + kdf.init(aes128Key, Signature.MODE_SIGN); + return kdf.sign(bufIn, bufInStart, buffInLength, bufOut, bufStart); + } + + @Override + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + return null; + } + + public ECPrivateKey createEcPrivateKey(byte[] pubBuffer, short pubOff, short pubLength, + byte[] privBuffer, short privOff, short privLength) { + // Simulator does not support NamedParameterSpec or 256 bit keys + ECPrivateKey privKey = (ECPrivateKey) ec192KeyPair.getPrivate(); + if(privLength > 24){ + privLength = 24;// simulator does not support more then 24 bytes - 192 bit key. + }else if(privLength <= 20){ + return null; + } + privKey.setS(privBuffer,privOff, privLength); + return privKey; + } + + @Override + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + // simulator only supports HMAC keys for SHA1 and SHA256 with block size 64. + // So only 128 and 256 bit HMAC keys are supported. + HMACKey key; + if (secretLength == 16) { + key = hmac128Key; + key.setKey(secretBuffer, secretOff, secretLength); + } else if (secretLength == 32) { + key = hmac256Key; + key.setKey(secretBuffer, secretOff, secretLength); + } else { + key = hmac128Key; // by default the simulator will return 128 bit keys for SHA1 + key.setKey(secretBuffer, secretOff, (short)16); + } + return key; + } + @Override + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + // only 128 bit keys are supported + if(secretLength < 128) return null; + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + @Override + public RSAPrivateKey createRsaKey(byte[] modBuffer, short modOff, short modLength, byte[] privBuffer, short privOff, short privLength) { + return null; + } + + @Override + public HMACKey cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength) { + return null; + } + + @Override + public short hmacSign(HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + return 0; + } + + @Override + public boolean hmacVerify(HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart, short macLength) { + return false; + } + + @Override + public KMCipher createRsaDecipher(short padding, byte[] secret, short secretStart, short secretLength, byte[] modBuffer, short modOff, short modLength) { + return null; + } + + @Override + public Signature createRsaSigner(short msgDigestAlg, short padding, byte[] secret, short secretStart, short secretLength, byte[] modBuffer, short modOff, short modLength) { + return null; + } + + @Override + public Signature createEcSigner(short msgDigestAlg, byte[] secret, short secretStart, short secretLength) { + return null; + } + + @Override + public KMCipher createSymmetricCipher(short cipherAlg, short padding, short mode, byte[] secret, short secretStart, short secretLength, byte[] ivBuffer, short ivStart, short ivLength) { + return null; + } + + @Override + public KMCipher createSymmetricCipher(short cipherAlg, short mode, short padding, byte[] secret, short secretStart, short secretLength) { + return null; + } + + @Override + public Signature createHmacSignerVerifier(short purpose, short msgDigestAlg, byte[] secret, short secretStart, short secretLength) { + return null; + } + + @Override + public KMCipher createGCMCipher(short mode, byte[] secret, short secretStart, short secretLength, byte[] ivBuffer, short ivStart, short ivLength) { + return null; + } + + @Override + public void delete(KMCipher cipher) { + + } + + + @Override + public void delete(Signature signature) { + + } + + @Override + public void delete(Key key) { + + } + + @Override + public void delete(KeyPair keyPair) { + + } + + public RSAPrivateKey createRsaPrivateKey(byte[] modBuffer, short modOff, short modLength, byte[] privBuffer, short privOff, short privLength) { + RSAPrivateKey privKey = (RSAPrivateKey) rsa512KeyPair.getPrivate(); + if(privLength > 64) privLength = 64; + else if(privLength < 64)return null; + if(modLength > 64) modLength = 64; + else if( modLength < 64) return null; + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + } + + private void initEntropyPool(byte[] pool) { + byte index = 0; + RandomData trng; + while (index < rngCounter.length) { + rngCounter[index++] = 0; + } + try { + trng = RandomData.getInstance(RandomData.ALG_TRNG); + trng.nextBytes(pool, (short) 0, (short) pool.length); + } catch (CryptoException exp) { + if (exp.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + // TODO change this when possible + // simulator does not support TRNG algorithm. So, PRNG algorithm (deprecated) is used. + trng = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); + trng.nextBytes(pool, (short) 0, (short) pool.length); + } else { + // TODO change this to proper error code + ISOException.throwIt(ISO7816.SW_UNKNOWN); + } + } + } + + // Generate a secure random number from existing entropy pool. This uses aes ecb algorithm with + // 8 byte rngCounter and 16 byte block size. + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + KMRepository repository = KMRepository.instance(); + byte[] bufPtr = repository.getHeap(); + short countBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short randBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short len = KMKeymasterApplet.AES_BLOCK_SIZE; + aesRngKey.setKey(entropyPool, (short) 0); + aesRngCipher.init(aesRngKey, Cipher.MODE_ENCRYPT, aesICV, (short) 0, (short) 16); + while (length > 0) { + if (length < len) len = length; + // increment rngCounter by one + incrementCounter(); + // copy the 8 byte rngCounter into the 16 byte rngCounter buffer. + Util.arrayCopy(rngCounter, (short) 0, bufPtr, countBufInd, (short) rngCounter.length); + // encrypt the rngCounter buffer with existing entropy which forms the aes key. + aesRngCipher.doFinal( + bufPtr, countBufInd, KMKeymasterApplet.AES_BLOCK_SIZE, bufPtr, randBufInd); + // copy the encrypted rngCounter block to buffer passed in the argument + Util.arrayCopy(bufPtr, randBufInd, num, startOff, len); + length = (short) (length - len); + startOff = (short) (startOff + len); + } + } + + // increment 8 byte rngCounter by one + private void incrementCounter() { + // start with least significant byte + short index = (short) (rngCounter.length - 1); + while (index >= 0) { + // if the msb of current byte is set then it will be negative + if (rngCounter[index] < 0) { + // then increment the rngCounter + rngCounter[index]++; + // is the msb still set? i.e. no carry over + if (rngCounter[index] < 0) break; // then break + else index--; // else go to the higher order byte + } else { + // if msb is not set then increment the rngCounter + rngCounter[index]++; + break; + } + } + } + @Override + public void bypassAesGcm(){ + jcardSim = true; + } + + @Override + public KMCipher createRsaCipher(short padding, byte[] modBuffer, short modOff, short modLength) { + return null; + } + + @Override + public Signature createRsaVerifier(short msgDigestAlg, short padding, byte[] modBuffer, short modOff, short modLength) { + return null; + } + @Override + public Signature createEcVerifier(short msgDigestAlg, byte[] pubKey, short pubKeyStart, short pubKeyLength) { + return null; + } + +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMAbortOperationCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMAbortOperationCmd.java deleted file mode 100644 index ee3e052a..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMAbortOperationCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMAbortOperationCmd extends KMAbstractCmd { - public static final byte INS_ABORT_OPERATION_CMD = 0x22; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_ABORT_OPERATION_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMAbstractCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMAbstractCmd.java deleted file mode 100644 index 02fad435..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMAbstractCmd.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public abstract class KMAbstractCmd implements KMCommand { - - /** - * Implements the KMCommand interface. - * - * @param context provides information required to execute the command. - */ - @Override - public void execute(KMContext context) { - // Assert the command's operational state - if (!this.validateState(context.getKeymasterState())) { - throw new KMException(KMException.CMD_NOT_ACCEPTED_WRONG_STATE); - } - KMEncoder encoder = context.getRepository().getEncoder(); - KMDecoder decoder = context.getRepository().getDecoder(); - // Get getExpectedArgs if expected - KMArray args = null; - if (hasArguments()) { - // Deserialize the getExpectedArgs - KMArray argsProto = getExpectedArgs(); - args = decoder.decode(argsProto, context.getBuffer(), (short) 0, context.getBufferLength()); - } - // Pass control to concrete command subclass - KMArray resp = this.process(args, context); - context.setBufferLength((short)0); - // If there is resp then serialize and send - if (resp != null) { - // set outgoing buffer - short len = encoder.encode(resp, context.getBuffer(), (short) 0, (short)context.getBuffer().length); - context.setBufferLength(len); - } - } - - /** - * Get the getExpectedArgs prototype expression from the concrete subclass. - * - * @return KMArray of KMType objects which provides expression for the command's getExpectedArgs.. - */ - protected abstract KMArray getExpectedArgs(); - - /** - * Implemented by the subclass to execute the command specific functionality. - * - * @param args which are decoded from the the apdu. - * @param context within which the command should be executed. - * @return Null or response having the result of the command's execution. - */ - protected abstract KMArray process(KMArray args, KMContext context); - - /** - * Validate the state required by the command to execute. By default all the commands can execute - * in active state. - * - * @param state is the current state of the applet - * @return true if the state is valid for command's execution else false is returned. - */ - protected boolean validateState(byte state) { - return (KMKeymasterApplet.ACTIVE_STATE == state); - } - - @Override - public boolean hasArguments(){ - return true; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMAddRngEntropyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMAddRngEntropyCmd.java deleted file mode 100644 index cb431b8f..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMAddRngEntropyCmd.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -import javacard.framework.ISO7816; -import javacard.framework.JCSystem; -import javacard.framework.Util; - -// This command adds entropy into the current entropy pool as per hal specifications. -public class KMAddRngEntropyCmd extends KMAbstractCmd { - public static final byte INS_ADD_RNG_ENTROPY_CMD = 0x18; - public static final short MAX_SEED_SIZE = 2048; - - @Override - protected KMArray getExpectedArgs() { - return KMArray.instance((short) 1).add((short) 0, KMByteBlob.instance()); - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - KMByteBlob blob = (KMByteBlob) args.get((short) 0); - // maximum 2KiB of seed is allowed. - if (blob.length() > MAX_SEED_SIZE) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMUtil.init(context.getRepository()); - // Get existing entropy pool. - byte[] entPool = context.getRepository().getEntropyPool(); - // Create new temporary pool. - byte[] heapRef = context.getRepository().getByteHeapRef(); - short poolStart = context.getRepository().newByteArray((short) entPool.length); - // Populate the new pool with the entropy which is derived from current entropy pool. - KMUtil.newRandomNumber(heapRef, poolStart, (short) entPool.length); - // Copy the entropy to the current pool - updates the entropy pool. - Util.arrayCopy(heapRef, poolStart, entPool, (short) 0, (short) entPool.length); - short index = 0; - short randIndex = 0; - // Mix (XOR) the seed received from the master in the entropy pool - 32 bytes (entPool.length). - // at a time. - while (index < blob.length()) { - entPool[randIndex] = (byte) (entPool[randIndex] ^ blob.get(index)); - randIndex++; - index++; - if (randIndex >= entPool.length) { - randIndex = 0; - } - } - // TODO return success error code. - return null; - } - - @Override - public byte getIns() { - return INS_ADD_RNG_ENTROPY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java b/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java index 23b5afe6..4646f46e 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -17,66 +17,79 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMArray extends KMType { - private KMType[] vals; - private short length; - private short startOff; + public static final short ANY_ARRAY_LENGTH = 0x1000; + // short Type + short Length + private static final short ARRAY_HEADER_SIZE = 4; + private static KMArray prototype; + private static short instPtr; - private KMArray() { - init(); + private KMArray() {} + + private static KMArray proto(short ptr) { + if (prototype == null) prototype = new KMArray(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - vals = null; - startOff = 0; - length = 0; + public static short exp() { + short ptr = instance(ARRAY_TYPE, ARRAY_HEADER_SIZE); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE),(short)0 ); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE + 2),ANY_ARRAY_LENGTH ); + return ptr; } - @Override - public short length() { - return length; + public static short exp(short type) { + short ptr = instance(ARRAY_TYPE, ARRAY_HEADER_SIZE); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE),type); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE + 2),ANY_ARRAY_LENGTH ); + return ptr; } - public static void create(KMArray[] arrayRefTable) { - byte index = 0; - while (index < arrayRefTable.length) { - arrayRefTable[index] = new KMArray(); - index++; - } + public static short instance(short length) { + short ptr = KMType.instance(ARRAY_TYPE, (short)(ARRAY_HEADER_SIZE + (length*2))); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE),(short)0); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE + 2),length); + return ptr; } - public static KMArray instance() { - return repository.newArray(); + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap,(short)(ptr + TLV_HEADER_SIZE),type); + return ptr; } - public static KMArray instance(short length) { + public static KMArray cast(short ptr) { + if (heap[ptr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); + } - KMArray inst = repository.newArray(); - inst.startOff = repository.newTypeArray(length); - inst.vals = repository.getTypeArrayRef(); - inst.length = length; - return inst; + public void add(short index, short objPtr) { + short len = length(); + if (index >= len) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + Util.setShort(heap, (short) (instPtr + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE + (short)(index*2)), objPtr) ; } - public KMArray withLength(short length) { - this.length = length; - return this; + public short get(short index) { + short len = length(); + if (index >= len) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + return Util.getShort(heap,(short) (instPtr + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE + (short)(index*2))); } - public KMArray add(short index, KMType val) { - if (index >= length) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - vals[(short) (startOff + index)] = val; - return this; + public short containedType(){ return Util.getShort(heap, (short)(instPtr + TLV_HEADER_SIZE));} + + public short getStartOff() { + return (short) (instPtr + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); + } + + public short length() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE + 2)); } - public KMType get(short index) { - if (index >= length) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - return vals[(short) (startOff + index)]; + public byte[] getBuffer() { + return heap; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMAttestKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMAttestKeyCmd.java deleted file mode 100644 index 9d51e231..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMAttestKeyCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMAttestKeyCmd extends KMAbstractCmd { - public static final byte INS_ATTEST_KEY_CMD = 0x14; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_ATTEST_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMAuthTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMAuthTag.java new file mode 100644 index 00000000..23d4f801 --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMAuthTag.java @@ -0,0 +1,7 @@ +package com.android.javacard.keymaster; + +public class KMAuthTag { + public boolean reserved; + public byte[] authTag; + public short usageCount; +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMBeginOperationCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMBeginOperationCmd.java deleted file mode 100644 index 2ab55233..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMBeginOperationCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMBeginOperationCmd extends KMAbstractCmd { - public static final byte INS_BEGIN_OPERATION_CMD = 0x1F; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_BEGIN_OPERATION_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java index 6cf41894..ae0f75ac 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -17,8 +17,12 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMBoolTag extends KMTag { + private static KMBoolTag prototype; + private static short instPtr; private static final short[] tags = { CALLER_NONCE, @@ -33,62 +37,53 @@ public class KMBoolTag extends KMTag { RESET_SINCE_ID_ROTATION }; - // Array of Tag Values. - private short key; - private byte val; + private KMBoolTag() {} - // assignBlob constructor - private KMBoolTag() { - init(); + private static KMBoolTag proto(short ptr) { + if (prototype == null) prototype = new KMBoolTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - val = 1; // always 1. + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short)2); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), BOOL_TAG); + return ptr; } - public static KMBoolTag instance() { - return repository.newBoolTag(); + public static short instance(short key) { + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short)5); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), BOOL_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + heap[(short)(ptr+TLV_HEADER_SIZE+4)] = 0x01; + return ptr; } - public static void create(KMBoolTag[] boolTagRefTable) { - byte index = 0; - while (index < boolTagRefTable.length) { - boolTagRefTable[index] = new KMBoolTag(); - index++; + public static KMBoolTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BOOL_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } + return proto(ptr); } - @Override public short getKey() { - return key; - } - - @Override - public short length() { - return 1; + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); } - @Override public short getTagType() { return KMType.BOOL_TAG; } public byte getVal() { - return val; - } - // create default assignBlob without any value - public static KMBoolTag instance(short key) { - if (!validateKey(key)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMBoolTag tag = repository.newBoolTag(); - tag.key = key; - return tag; + return heap[(short)(instPtr+TLV_HEADER_SIZE+4)]; } - // validate the tag key + // isValidTag the tag key private static boolean validateKey(short key) { short index = (short) tags.length; while (--index >= 0) { @@ -98,4 +93,7 @@ private static boolean validateKey(short key) { } return false; } + public static short[] getTags(){ + return tags; + } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java index fca5b947..7f598eaa 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -17,95 +17,96 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; import javacard.framework.Util; -// Byte val represents contiguous memory buffer. +// Byte blob represents contiguous memory buffer. public class KMByteBlob extends KMType { - private byte[] val; - private short startOff; - private short length; + private static KMByteBlob prototype; + private static short instPtr; - private KMByteBlob() { - init(); + private KMByteBlob() {} + + private static KMByteBlob proto(short ptr) { + if (prototype == null) prototype = new KMByteBlob(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - length = 0; - startOff = 0; - val = null; + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(BYTE_BLOB_TYPE); } - @Override - public short length() { - return length; + // return an empty byte blob instance + public static short instance(short length) { + return KMType.instance(BYTE_BLOB_TYPE, length); } - public static KMByteBlob instance() { - return repository.newByteBlob(); + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic(buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE), length); + return ptr; } - // copy the blob - public static KMByteBlob instance(byte[] blob, short startOff, short length) { - if ((length <= 0) || ((short)(startOff+length) > blob.length)) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + // cast the ptr to KMByteBlob + public static KMByteBlob cast(short ptr) { + if (heap[ptr] != BYTE_BLOB_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - KMByteBlob inst = instance(length); - Util.arrayCopyNonAtomic(blob, startOff, inst.val, inst.startOff, inst.length); - return inst; + return proto(ptr); } - // returns empty blob with given length - public static KMByteBlob instance(short length) { - if (length <= 0) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMByteBlob inst = instance(); - inst.startOff = repository.newByteArray(length); - inst.val = repository.getByteHeapRef(); - inst.length = length; - return inst; + // Add the byte + public void add(short index, byte val) { + short len = length(); + if (index >= len) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + heap[(short) (instPtr + TLV_HEADER_SIZE + index)] = val; } - public static void create(KMByteBlob[] byteBlobRefTable) { - byte index = 0; - while (index < byteBlobRefTable.length) { - byteBlobRefTable[index] = new KMByteBlob(); - index++; - } + // Get the byte + public byte get(short index) { + short len = length(); + if (index >= len) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + return heap[(short) (instPtr + TLV_HEADER_SIZE + index)]; } - // sets the expected length for prototype byte val. - public KMByteBlob withLength(short len) { - this.length = len; - return this; + // Get the start of blob + public short getStartOff() { + return (short) (instPtr + TLV_HEADER_SIZE); } - public void add(short index, byte val) { - if (index >= this.length) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - if (this.val == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - this.val[(short) (startOff + index)] = val; + // Get the length of the blob + public short length() { + return Util.getShort(heap, (short) (instPtr + 1)); } - public byte get(short index) { - if (index >= this.length) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - if (this.val == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - return this.val[(short) (startOff + index)]; + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; } - public byte[] getVal() { - return val; + public void getValue(byte[] destBuf, short destStart, short destLength){ + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + } + public short getValues(byte[] destBuf, short destStart){ + short destLength = length(); + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + return destLength; } - public short getStartOff() { - return startOff; + public void setValue(byte[] srcBuf, short srcStart, short srcLength){ + if(length() > srcLength){ + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.arrayCopyNonAtomic(srcBuf, srcStart, heap, getStartOff(), length()); + } + public boolean isValid(){ + if (length() == 0) { + return false; + } + return true; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java index c415abbc..a00d6c6f 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -17,8 +17,12 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMByteTag extends KMTag { + private static KMByteTag prototype; + private static short instPtr; private static final short[] tags = { APPLICATION_ID, @@ -37,73 +41,73 @@ public class KMByteTag extends KMTag { ATTESTATION_ID_MODEL, ASSOCIATED_DATA, NONCE, - CONFIRMATION_TOKEN + CONFIRMATION_TOKEN, + VERIFIED_BOOT_KEY, + VERIFIED_BOOT_HASH }; - private short key; - private KMByteBlob val; + private KMByteTag() {} - private KMByteTag() { - init(); + private static KMByteTag proto(short ptr) { + if (prototype == null) prototype = new KMByteTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - val = null; + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), INVALID_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), blobPtr); + return ptr; } - @Override - public short getKey() { - return key; - } - - @Override - public short length() { - return val.length(); + public static short instance(short key) { + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return instance(key, KMByteBlob.exp()); } - @Override - public short getTagType() { - return KMType.BYTES_TAG; + public static short instance(short key, short byteBlob) { + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if(heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), byteBlob); + return ptr; } - public static KMByteTag instance() { - return repository.newByteTag(); + public static KMByteTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BYTES_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - public static KMByteTag instance(short key) { - if (!validateKey(key)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMByteTag tag = repository.newByteTag(); - tag.key = key; - tag.val = null; - return tag; + public short getKey() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); } - public static void create(KMByteTag[] byteTagRefTable) { - byte index = 0; - while (index < byteTagRefTable.length) { - byteTagRefTable[index] = new KMByteTag(); - index++; - } + public short getTagType() { + return KMType.BYTES_TAG; } - // create default assignBlob without any value - public static KMByteTag instance(short key, KMByteBlob array) { - if (!validateKey(key)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMByteTag tag = repository.newByteTag(); - tag.key = key; - tag.val = array; - return tag; + public short getValue() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); } - public KMByteTag withLength(short length) { - this.val.withLength(length); - return this; + public short length() { + short blobPtr = Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); + return KMByteBlob.cast(blobPtr).length(); } private static boolean validateKey(short key) { @@ -115,13 +119,4 @@ private static boolean validateKey(short key) { } return false; } - - public KMByteBlob getValue() { - return val; - } - - public KMByteTag setValue(KMByteBlob val) { - this.val = val; - return this; - } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMCipher.java b/Applet/Applet/src/com/android/javacard/keymaster/KMCipher.java new file mode 100644 index 00000000..08cdf789 --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMCipher.java @@ -0,0 +1,35 @@ +package com.android.javacard.keymaster; + +public abstract class KMCipher { + public static final byte CIPHER_RSA = 7; + public static final short PAD_PKCS1_OAEP_SHA224 = 13; + public static final byte PAD_PKCS1_OAEP_SHA256 = 14; + public static final short PAD_PKCS1_OAEP_SHA384 = 15; + public static final short PAD_PKCS1_OAEP_SHA512 = 16; + public static final short PAD_NOPAD = 1; + public static final short PAD_PKCS1_PSS = 8; + public static final short PAD_NULL = 0; + public static final short PAD_PKCS7 = 31; // Not supported in javacard + public static final short ALG_DES_CBC_NOPAD = 1; + public static final short ALG_DES_ECB_NOPAD = 5; + public static final short ALG_AES_BLOCK_128_CBC_NOPAD= 13; + public static final short ALG_AES_BLOCK_128_ECB_NOPAD = 14; + public static final short MODE_ENCRYPT = 2; + public static final short MODE_DECRYPT = 1; + public static final short PAD_PKCS1 = 7; + public static final short AES_BLOCK_SIZE = 16; + public static final short DES_BLOCK_SIZE = 8; + + public abstract short doFinal(byte[] buffer, short startOff, short length, byte[] scratchPad, short i); + + public abstract short update(byte[] buffer, short startOff, short length, byte[] scratchPad, short i); + + public abstract short getPaddingAlgorithm(); + + public abstract short getCipherAlgorithm(); + + public abstract void setPaddingAlgorithm(short alg); + + public abstract void setCipherAlgorithm(short alg); + +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMCommand.java b/Applet/Applet/src/com/android/javacard/keymaster/KMCommand.java deleted file mode 100644 index 9e17dd58..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -import javacard.framework.APDU; - -/** This interface declares methods to be implemented by the command instances. */ -public interface KMCommand { - /** - * Execute this command within given context. If the command fails then it throws an exception - * - * @param context provides information required to execute the command. - */ - void execute(KMContext context); - - /** - * Return the instruction code associated with this command. The implementations will provide this - * code. - * - * @return instruction code which is related APDU INS. - */ - byte getIns(); - - /** - * Indicates whether command has arguments. - * @ return true if the command has arguments - */ - boolean hasArguments(); -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMComputeSharedHmacCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMComputeSharedHmacCmd.java deleted file mode 100644 index 5d2e0b46..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMComputeSharedHmacCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMComputeSharedHmacCmd extends KMAbstractCmd { - public static final byte INS_COMPUTE_SHARED_HMAC_CMD = 0x19; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_COMPUTE_SHARED_HMAC_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMContext.java b/Applet/Applet/src/com/android/javacard/keymaster/KMContext.java deleted file mode 100644 index 0d081f89..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMContext.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -/** - * This class provides data structure for information which is passed between the Keymaster Applet - * and the commands. It is created by applet and initialized for the process request. Applet sets - * repository, apdu and keymasterState. Command sets and uses incoming buffer information, outgoing - * buffer information and operation state (if command is an operation). - */ -public class KMContext { - private KMRepository repository; - private byte keymasterState; - private KMOperationState opState; - private byte[] buffer; - private short bufferLength; - /** - * Setter for the keymasterState. Set by the applet. - * - * @param keymasterState represents current applet state. - */ - public void setKeymasterState(byte keymasterState) { - this.keymasterState = keymasterState; - } - - /** - * Getter for keymasterState. Used by the commands. - * - * @return keymasterState represents current applets state. - */ - public byte getKeymasterState() { - return keymasterState; - } - - - /** - * Getter for buffer used for receiving or sending data to or from the master. Used by the - * messenger. - * - * @return buffer which is used to copying data to and from apdu's buffer. Start offset is always - * 0. - */ - public byte[] getBuffer() { - return buffer; - } - - /** - * Setter for buffer. Used by the repository. - * - * @param buffer which is used to copying data to and from apdu's buffer. - */ - public void setBuffer(byte[] buffer) { - this.buffer = buffer; - } - - /** - * Getter for buffer length. Used by the messenger and commands. - * - * @return buffer length. - */ - public short getBufferLength() { - return bufferLength; - } - - /** - * Setter for buffer length. Used by the messenger commands. - * - * @param length of buffer. - */ - public void setBufferLength(short length) { - this.bufferLength = length; - } - - /** - * Getter for repository instance. Used by commands. - * - * @return repository - */ - public KMRepository getRepository() { - return repository; - } - - /** - * Setter for the repository instance. Used by the applet. - * - * @param repository is repository of the KMType objects and other objects. - */ - public void setRepository(KMRepository repository) { - this.repository = repository; - } - - /** - * Getter for the OperationState for operation specific commands. Used by commands. - * - * @return Operation state associated with the command. - */ - public KMOperationState getOpState() { - return opState; - } - - /** Setter for the OperationState for operation specific commands. Used by commands. */ - public void setOpState(KMOperationState opState) { - this.opState = opState; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMCryptoProvider.java b/Applet/Applet/src/com/android/javacard/keymaster/KMCryptoProvider.java new file mode 100644 index 00000000..44fd05e7 --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMCryptoProvider.java @@ -0,0 +1,115 @@ +package com.android.javacard.keymaster; + +import javacard.security.AESKey; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyPair; +import javacard.security.RSAPrivateKey; +import javacard.security.Signature; + +public interface KMCryptoProvider { + KeyPair createRsaKeyPair(); + + KeyPair createECKeyPair(); + + AESKey createAESKey(short keysize); + + AESKey createAESKey(byte[] buf, short startOff, short length); + + DESKey createTDESKey(); + + HMACKey createHMACKey(short keysize); + + void newRandomNumber(byte[] num, short offset, short length); + + void addRngEntropy(byte[] num, short offset, short length); + + short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + boolean aesGCMDecrypt( + AESKey key, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + byte[] getTrueRandomNumber(short len); + + short aesCCMSign( + byte[] bufIn, + short bufInStart, + short buffInLength, + byte[] masterKeySecret, + byte[] bufOut, + short bufStart); + + ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength); + + HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength); + + DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength); + + RSAPrivateKey createRsaKey(byte[] modBuffer, short modOff, short modLength, + byte[] privBuffer, short privOff, short privLength); + + HMACKey cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength); + + short hmacSign(HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart); + boolean hmacVerify(HMACKey key, byte[] data, short dataStart, short dataLength, + byte[] mac, short macStart, short macLength); + + KMCipher createRsaDecipher(short padding, + byte[] secret, short secretStart, short secretLength, + byte[] modBuffer, short modOff, short modLength); + Signature createRsaSigner(short msgDigestAlg, short padding, byte[] secret, short secretStart, + short secretLength, byte[] modBuffer, short modOff, short modLength); + Signature createEcSigner(short msgDigestAlg, byte[] secret, short secretStart, + short secretLength); + KMCipher createSymmetricCipher(short cipherAlg, short mode, short padding, + byte[] secret, short secretStart, short secretLength, + byte[] ivBuffer, short ivStart, short ivLength); + KMCipher createSymmetricCipher(short cipherAlg, short mode,short padding, + byte[] secret, short secretStart, short secretLength); + Signature createHmacSignerVerifier(short purpose, short msgDigestAlg, + byte[] secret, short secretStart, short secretLength); + KMCipher createGCMCipher(short mode, byte[] secret, short secretStart, short secretLength, + byte[] ivBuffer, short ivStart, short ivLength); + void delete(KMCipher cipher); + void delete(Signature signature); + void delete(Key key); + void delete(KeyPair keyPair); + //TODO remove this later + void bypassAesGcm(); + + KMCipher createRsaCipher(short padding, byte[] buffer, short startOff, short length); + Signature createRsaVerifier(short msgDigestAlg, short padding, byte[] modBuffer, + short modOff, short modLength); + Signature createEcVerifier(short msgDigestAlg, byte[] pubKey, short pubKeyStart, short pubKeyLength); +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java index 4cedfcaf..a1ced7cd 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -17,8 +17,9 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; import javacard.framework.Util; -// TODO Clean and refactor the code. + public class KMDecoder { // major types private static final short UINT_TYPE = 0x00; @@ -36,7 +37,6 @@ public class KMDecoder { private static final short UINT32_LENGTH = 0x1A; private static final short UINT64_LENGTH = 0x1B; - // TODO move the following to transient memory. private byte[] buffer; private short startOff; private short length; @@ -49,67 +49,215 @@ public KMDecoder() { length = 0; } - public KMArray decode(KMArray expression, byte[] buffer, short startOff, short length) { + public short decode(short expression, byte[] buffer, short startOff, short length) { this.buffer = buffer; this.startOff = startOff; - this.length = length; + this.length = (short)(startOff+length); return decode(expression); } + public short decodeArray(short exp, byte[] buffer, short startOff, short length){ + this.buffer = buffer; + this.startOff = startOff; + this.length = (short)(startOff+length); + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short expLength = KMArray.cast(exp).length(); + if(payloadLength > expLength){ + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short index = 0; + short obj; + short type; + short arrPtr = KMArray.instance(payloadLength); + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + return arrPtr; + } + private short decode(short exp){ + byte type = KMType.getType(exp); + switch(type){ + case KMType.BYTE_BLOB_TYPE: + return decodeByteBlob(exp); + case KMType.INTEGER_TYPE: + return decodeInteger(exp); + case KMType.ARRAY_TYPE: + return decodeArray(exp); + case KMType.ENUM_TYPE: + return decodeEnum(exp); + case KMType.KEY_PARAM_TYPE: + return decodeKeyParam(exp); + case KMType.KEY_CHAR_TYPE: + return decodeKeyChar(exp); + case KMType.VERIFICATION_TOKEN_TYPE: + return decodeVerificationToken(exp); + case KMType.HMAC_SHARING_PARAM_TYPE: + return decodeHmacSharingParam(exp); + case KMType.HW_AUTH_TOKEN_TYPE: + return decodeHwAuthToken(exp); + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + return decodeTag(tagType, exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + private short decodeTag(short tagType, short exp){ + switch(tagType){ + case KMType.BYTES_TAG: + return decodeBytesTag(exp); + case KMType.BOOL_TAG: + return decodeBoolTag(exp); + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + return decodeIntegerTag(exp); + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + return decodeIntegerArrayTag(exp); + case KMType.ENUM_TAG: + return decodeEnumTag(exp); + case KMType.ENUM_ARRAY_TAG: + return decodeEnumArrayTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeVerificationToken(short exp) { + short vals = decode(KMVerificationToken.cast(exp).getVals()); + return KMVerificationToken.instance(vals); + } + + private short decodeHwAuthToken(short exp) { + short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + return KMHardwareAuthToken.cast(exp).instance(vals); + } + + private short decodeHmacSharingParam(short exp) { + short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + return KMHmacSharingParameters.instance(vals); + } + + private short decodeKeyChar(short exp) { + short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + return KMKeyCharacteristics.instance(vals); + } + + private short decodeKeyParam(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // allowed tags + short allowedTags = KMKeyParameters.cast(exp).getVals(); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedTags).length(); + short index = 0; + boolean tagFound; + short tagInd; + short tagType; + short tagClass; + short allowedType; + short obj; + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + tagType = peekTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedTags).get(tagInd); + allowedType = KMTag.getTagType(tagClass); + // If it is part of allowed tags ... + if (tagType == allowedType) { + // then decodeByteBlob and add that to the array. + obj = decode(tagClass); + KMArray.cast(vals).add(index,obj); + tagFound = true; + break; + } + tagInd++; + } + if(!tagFound){ + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + else { + index++; + } + } + return KMKeyParameters.instance(vals); + } - private KMEnumArrayTag decode(KMEnumArrayTag exp) { - readTagKey(exp.getTagType()); + private short decodeEnumArrayTag(short exp) { + readTagKey(KMEnumArrayTag.cast(exp).getTagType()); // The value must be byte blob // TODO check this out. - return exp.instance(this.tagKey, decode(exp.getValues())); + return KMEnumArrayTag.instance(this.tagKey, decode(KMEnumArrayTag.cast(exp).getValues())); } - private KMIntegerArrayTag decode(KMIntegerArrayTag exp) { - readTagKey(exp.getTagType()); + private short decodeIntegerArrayTag(short exp) { + readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); // the values are array of integers. - if (!(exp.getValues().getType() instanceof KMInteger)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - return exp.instance(this.tagKey, decode(exp.getValues(), (KMInteger) exp.getValues().getType())); + return KMIntegerArrayTag.instance(KMIntegerArrayTag.cast(exp).getTagType(), + this.tagKey, decode(KMIntegerArrayTag.cast(exp).getValues())); } - private KMIntegerTag decode(KMIntegerTag exp) { - readTagKey(exp.getTagType()); + private short decodeIntegerTag(short exp) { + readTagKey(KMIntegerTag.cast(exp).getTagType()); // the value is an integer - return exp.instance(this.tagKey, decode(exp.getValue())); + return KMIntegerTag.instance(KMIntegerTag.cast(exp).getTagType(), + this.tagKey, decode(KMIntegerTag.cast(exp).getValue())); } - private KMByteTag decode(KMByteTag exp) { - short key = 0; - readTagKey(exp.getTagType()); + private short decodeBytesTag(short exp) { + readTagKey(KMByteTag.cast(exp).getTagType()); // The value must be byte blob - return exp.instance(this.tagKey, decode(exp.getValue())); + return KMByteTag.instance(this.tagKey, decode(KMByteTag.cast(exp).getValue())); } - private KMBoolTag decode(KMBoolTag exp) { - readTagKey(exp.getTagType()); - // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. - // TODO check this out. - if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { - throw new KMException(ISO7816.SW_DATA_INVALID); + private short decodeArray(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short arrPtr = KMArray.cast(exp).instance(payloadLength); + short index = 0; + short type; + short obj; + // check whether array contains one type of objects or multiple types + if( KMArray.cast(exp).containedType() == 0){// multiple types specified by expression. + if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.cast(exp).length() != payloadLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + } + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + }else{ // Array is a Vector containing objects of one type + type = KMArray.cast(exp).containedType(); + while(index < payloadLength){ + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } } - incrementStartOff((short) 1); - return exp.instance(tagKey); + return arrPtr; } - private KMEnumTag decode(KMEnumTag exp) { - readTagKey(exp.getTagType()); + private short decodeEnumTag(short exp) { + readTagKey(KMEnumTag.cast(exp).getTagType()); // Enum Tag value will always be integer with max 1 byte length. // TODO Check this out. if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } short len = (short) (buffer[startOff] & ADDITIONAL_MASK); byte enumVal = 0; if (len > UINT8_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } if (len < UINT8_LENGTH) { enumVal = (byte)(len & ADDITIONAL_MASK); @@ -119,19 +267,32 @@ private KMEnumTag decode(KMEnumTag exp) { enumVal = buffer[startOff]; incrementStartOff((short) 1); } - return exp.instance(tagKey, enumVal); + return KMEnumTag.instance(tagKey, enumVal); } - private KMEnum decode(KMEnum exp) { + private short decodeBoolTag(short exp) { + readTagKey(KMBoolTag.cast(exp).getTagType()); + // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. + // TODO check this out. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + incrementStartOff((short) 1); + return KMBoolTag.instance(tagKey); + } + private short decodeEnum(short exp) { // Enum value will always be integer with max 1 byte length. if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } short len = (short) (buffer[startOff] & ADDITIONAL_MASK); - byte enumVal = 0; + byte enumVal; if (len > UINT8_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } if (len < UINT8_LENGTH) { enumVal = (byte)(len & ADDITIONAL_MASK); @@ -141,175 +302,51 @@ private KMEnum decode(KMEnum exp) { enumVal = buffer[startOff]; incrementStartOff((short) 1); } - return exp.instance(exp.getType(), enumVal); + return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); } - private KMInteger decode(KMInteger exp) { - KMInteger inst; + private short decodeInteger(short exp) { + short inst; if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if(len > UINT64_LENGTH){ + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } incrementStartOff((short) 1); if (len < UINT8_LENGTH) { - inst = exp.uint_8((byte)(len & ADDITIONAL_MASK)); + inst = KMInteger.uint_8((byte)(len & ADDITIONAL_MASK)); } else if (len == UINT8_LENGTH) { - inst = exp.instance(buffer, startOff, (short) 1); + inst = KMInteger.instance(buffer, startOff, (short) 1); incrementStartOff((short) 1); } else if (len == UINT16_LENGTH) { - inst = exp.instance(buffer, startOff, (short) 2); + inst = KMInteger.instance(buffer, startOff, (short) 2); incrementStartOff((short) 2); } else if (len == UINT32_LENGTH) { - inst = exp.instance(buffer, startOff, (short) 4); + inst = KMInteger.instance(buffer, startOff, (short) 4); incrementStartOff((short) 4); - } else if (len == UINT64_LENGTH) { - inst = exp.instance(buffer, startOff, (short) 8); - incrementStartOff((short) 8); } else { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + inst = KMInteger.instance(buffer, startOff, (short) 8); + incrementStartOff((short) 8); } return inst; } - private KMByteBlob decode(KMByteBlob exp) { + private short decodeByteBlob(short exp) { short payloadLength = readMajorTypeWithPayloadLength(BYTES_TYPE); - KMByteBlob inst = exp.instance(buffer, startOff, payloadLength); + short inst = KMByteBlob.instance(buffer, startOff, payloadLength); incrementStartOff(payloadLength); return inst; } - private KMArray decode(KMArray exp) { - short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); - if (exp.length() != payloadLength) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMArray inst = exp.instance(payloadLength); - short index = 0; - while (index < payloadLength) { - KMType type = exp.get(index); - inst.add(index, decode(type)); - index++; - } - return inst; - } - - private KMVector decode(KMVector exp, KMInteger type) { - short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); - KMVector inst = exp.instance(type, payloadLength); - short index = 0; - while (index < payloadLength) { - inst.add(index, decode(type)); - index++; - } - return inst; - } - - private KMVerificationToken decode(KMVerificationToken exp) { - KMArray vals = decode(exp.getVals()); - return exp.instance(vals); - } - - private KMHardwareAuthToken decode(KMHardwareAuthToken exp) { - KMArray vals = decode(exp.getVals()); - return exp.instance(vals); - } - - private KMHmacSharingParameters decode(KMHmacSharingParameters exp) { - KMArray vals = decode(exp.getVals()); - return exp.instance(vals); - } - - private KMKeyParameters decode(KMKeyParameters exp) { - short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); - // allowed tags - // TODO expand the logic to handle prototypes with tag values also. - KMArray allowedTags = exp.getVals(); - KMArray vals = KMArray.instance(payloadLength); - short index = 0; - while (index < payloadLength) { - short tagInd = 0; - short tagType = peekTagType(); - while (tagInd < allowedTags.length()) { - KMTag tagClass = ((KMTag) allowedTags.get(tagInd)); - short allowedType = ((KMTag) allowedTags.get(tagInd)).getTagType(); - if (tagType == allowedType) { - vals.add(index, decode(tagClass)); - break; - } - tagInd++; - } - index++; - } - return KMKeyParameters.instance(vals); - } - - private KMKeyCharacteristics decode(KMKeyCharacteristics exp) { - KMArray vals = decode(exp.getVals()); - return exp.instance(vals); - } - - private KMType decode(KMType exp) { - if (exp instanceof KMByteBlob) { - return decode((KMByteBlob) exp); - } - if (exp instanceof KMInteger) { - return decode((KMInteger) exp); - } - if (exp instanceof KMArray) { - return decode((KMArray) exp); - } - if (exp instanceof KMVector) { - if (!((((KMVector) exp).getType()) instanceof KMInteger)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - return decode((KMVector) exp, (KMInteger) ((KMVector) exp).getType()); - } - if (exp instanceof KMByteTag) { - return decode((KMByteTag) exp); - } - if (exp instanceof KMBoolTag) { - return decode((KMBoolTag) exp); - } - if (exp instanceof KMIntegerTag) { - return decode((KMIntegerTag) exp); - } - if (exp instanceof KMIntegerArrayTag) { - return decode((KMIntegerArrayTag) exp); - } - if (exp instanceof KMEnumTag) { - return decode((KMEnumTag) exp); - } - if (exp instanceof KMEnum) { - return decode((KMEnum) exp); - } - if (exp instanceof KMEnumArrayTag) { - return decode((KMEnumArrayTag) exp); - } - if (exp instanceof KMKeyParameters) { - return decode((KMKeyParameters) exp); - } - if (exp instanceof KMKeyCharacteristics) { - return decode((KMKeyCharacteristics) exp); - } - if (exp instanceof KMVerificationToken) { - return decode((KMVerificationToken) exp); - } - if (exp instanceof KMHmacSharingParameters) { - return decode((KMHmacSharingParameters) exp); - } - if (exp instanceof KMHardwareAuthToken) { - return decode((KMHardwareAuthToken) exp); - } - throw new KMException(ISO7816.SW_DATA_INVALID); - } - private short peekTagType() { if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } if ((short) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } return (short) ((Util.makeShort(buffer[(short) (startOff + 1)], buffer[(short) (startOff + 2)])) @@ -318,16 +355,16 @@ private short peekTagType() { private void readTagKey(short expectedTagType) { if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } incrementStartOff((short) 1); this.tagType = readShort(); this.tagKey = readShort(); if (tagType != expectedTagType) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } @@ -336,11 +373,11 @@ private short readMajorTypeWithPayloadLength(short majorType) { short payloadLength = 0; byte val = readByte(); if ((short) (val & MAJOR_TYPE_MASK) != majorType) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } short lenType = (short) (val & ADDITIONAL_MASK); if (lenType > UINT16_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } if (lenType < UINT8_LENGTH) { payloadLength = lenType; @@ -367,7 +404,7 @@ private byte readByte() { private void incrementStartOff(short inc) { startOff += inc; if (startOff > this.length) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteAllKeysCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteAllKeysCmd.java deleted file mode 100644 index 5c476cd0..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteAllKeysCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMDeleteAllKeysCmd extends KMAbstractCmd { - public static final byte INS_DELETE_ALL_KEYS_CMD = 0x17; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_DELETE_ALL_KEYS_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteKeyCmd.java deleted file mode 100644 index ced6e7e4..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMDeleteKeyCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMDeleteKeyCmd extends KMAbstractCmd { - public static final byte INS_DELETE_KEY_CMD = 0x16; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_DELETE_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMDestroyAttestationIdsCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMDestroyAttestationIdsCmd.java deleted file mode 100644 index fa66ab1f..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMDestroyAttestationIdsCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMDestroyAttestationIdsCmd extends KMAbstractCmd { - public static final byte INS_DESTROY_ATT_IDS_CMD = 0x1A; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_DESTROY_ATT_IDS_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java index a231563b..01fb137a 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -17,6 +17,9 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.SystemException; import javacard.framework.Util; public class KMEncoder { @@ -42,177 +45,214 @@ public class KMEncoder { private byte[] buffer; private short startOff; private short length; + private static short[] stack; + private static byte stackPtr; public KMEncoder() { buffer = null; startOff = 0; length = 0; + stack = JCSystem.makeTransientShortArray((short)50, JCSystem.CLEAR_ON_RESET); } - public short encode(KMArray object, byte[] buffer, short startOff, short length) { + private static void push (short objPtr){ + stack[stackPtr] = objPtr; + stackPtr++; + } + private static short pop(){ + stackPtr--; + return stack[stackPtr]; + } + private void encode(short obj){ + push(obj); + } + public short encode(short object, byte[] buffer, short startOff) { + stackPtr = 0; this.buffer = buffer; this.startOff = startOff; - this.length = length; - encode(object); - this.length = this.startOff; - this.startOff = startOff; - return this.length; + short len = (short) buffer.length; + if((len <0) || (len > KMKeymasterApplet.MAX_LENGTH)){ + this.length = KMKeymasterApplet.MAX_LENGTH; + }else{ + this.length = (short)buffer.length; + } + //this.length = (short)(startOff + length); + push(object); + encode(); + return (short)(this.startOff - startOff); } - private void encode(KMType exp){ - if(exp instanceof KMByteBlob){ - encode((KMByteBlob) exp); - return; - } - if(exp instanceof KMEnum){ - encode((KMEnum) exp); - return; - } - if(exp instanceof KMInteger){ - encode((KMInteger)exp); - return; - } - if(exp instanceof KMArray){ - encode((KMArray)exp); - return; - } - if(exp instanceof KMVector){ - encode((KMVector)exp); - return; - } - if(exp instanceof KMByteTag){ - encode((KMByteTag)exp); - return; - } - if(exp instanceof KMBoolTag){ - encode((KMBoolTag) exp); - return; - } - if(exp instanceof KMIntegerTag){ - encode((KMIntegerTag)exp); - return; - } - if(exp instanceof KMIntegerArrayTag){ - encode((KMIntegerArrayTag)exp); - return; - } - if(exp instanceof KMEnumTag){ - encode((KMEnumTag) exp); - return; - } - if(exp instanceof KMEnumArrayTag){ - encode((KMEnumArrayTag) exp); - return; - } - if(exp instanceof KMKeyParameters){ - encode((KMKeyParameters) exp); - return; - } - if(exp instanceof KMKeyCharacteristics){ - encode((KMKeyCharacteristics) exp); - return; - } - if(exp instanceof KMVerificationToken){ - encode((KMVerificationToken) exp); - return; + + public short encodeError(short err, byte[] buffer, short startOff, short length) { + this.buffer = buffer; + this.startOff = startOff; + this.length = (short)(startOff + length); + // encode the err as UINT with value in err - should not be greater then 5 bytes. + if(err < UINT8_LENGTH){ + writeByte((byte)(UINT_TYPE | err )); + }else if(err < 0x100){ + writeByte((byte)(UINT_TYPE | UINT8_LENGTH)); + writeByte((byte)err); + }else { + writeByte((byte)(UINT_TYPE | UINT16_LENGTH)); + writeShort(err); } - if(exp instanceof KMHmacSharingParameters){ - encode((KMHmacSharingParameters) exp); - return; + return (short)(this.startOff - startOff); + } + + private void encode(){ + while (stackPtr > 0) { + short exp = pop(); + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + encodeByteBlob(exp); + break; + case KMType.INTEGER_TYPE: + encodeInteger(exp); + break; + case KMType.ARRAY_TYPE: + encodeArray(exp); + break; + case KMType.ENUM_TYPE: + encodeEnum(exp); + break; + case KMType.KEY_PARAM_TYPE: + encodeKeyParam(exp); + break; + case KMType.KEY_CHAR_TYPE: + encodeKeyChar(exp); + break; + case KMType.VERIFICATION_TOKEN_TYPE: + encodeVeriToken(exp); + break; + case KMType.HMAC_SHARING_PARAM_TYPE: + encodeHmacSharingParam(exp); + break; + case KMType.HW_AUTH_TOKEN_TYPE: + encodeHwAuthToken(exp); + break; + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + encodeTag(tagType, exp); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } } - if(exp instanceof KMHardwareAuthToken){ - encode((KMHardwareAuthToken) exp); - return; + } + private void encodeTag(short tagType, short exp){ + switch(tagType){ + case KMType.BYTES_TAG: + encodeBytesTag(exp); + return; + case KMType.BOOL_TAG: + encodeBoolTag(exp); + return; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + encodeIntegerTag(exp); + return; + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + encodeIntegerArrayTag(exp); + return; + case KMType.ENUM_TAG: + encodeEnumTag(exp); + return; + case KMType.ENUM_ARRAY_TAG: + encodeEnumArrayTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } - throw new KMException(ISO7816.SW_DATA_INVALID); } - private void encode(KMKeyParameters obj) { - encodeAsMap(obj.getVals()); + private void encodeKeyParam(short obj) { + encodeAsMap(KMKeyParameters.cast(obj).getVals()); } - private void encode(KMKeyCharacteristics obj) { - encode(obj.getVals()); + private void encodeKeyChar(short obj) { + encode(KMKeyCharacteristics.cast(obj).getVals()); } - private void encode(KMVerificationToken obj) { - encode(obj.getVals()); + private void encodeVeriToken(short obj) { + encode(KMVerificationToken.cast(obj).getVals()); } - private void encode(KMHardwareAuthToken obj) { - encode(obj.getVals()); + private void encodeHwAuthToken(short obj) { + encode(KMHardwareAuthToken.cast(obj).getVals()); } - private void encode(KMHmacSharingParameters obj) { - encode(obj.getVals()); + private void encodeHmacSharingParam(short obj) { + encode(KMHmacSharingParameters.cast(obj).getVals()); } - private void encode(KMArray obj) { - writeMajorTypeWithLength(ARRAY_TYPE, obj.length()); - short index = 0; - while(index < obj.length()){ - encode(obj.get(index)); - index++; + private void encodeArray(short obj) { + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short)(len-1); + while(index >= 0){ + encode(KMArray.cast(obj).get(index)); + index--; } } - private void encodeAsMap(KMArray obj){ - writeMajorTypeWithLength(MAP_TYPE, obj.length()); - short index = 0; - while(index < obj.length()){ - KMType t = obj.get(index); - encode(t); - //encode(obj.get(index)); - index++; + private void encodeAsMap(short obj){ + writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short)(len-1); + short inst; + while(index >= 0){ + inst = KMArray.cast(obj).get(index); + encode(inst); + index--; } } - private void encode(KMVector obj){ - writeMajorTypeWithLength(ARRAY_TYPE, obj.length()); - short index = 0; - while(index 0){ + if(val[(short)(startOff + index)] > 0){ + break; + }else if(val[(short)(startOff + index)] < 0){ break; } index++; // index will be equal to len if value is 0. @@ -221,34 +261,36 @@ private void encode(KMInteger obj) { short diff = (short)(len - index); if(diff == 0){ writeByte((byte)(UINT_TYPE | 0)); - }else if((diff == 1) && val[index] < UINT8_LENGTH){ - writeByte((byte)(UINT_TYPE | val[index])); + }else if((diff == 1) && (val[(short)(startOff + index)] < UINT8_LENGTH) + &&(val[(short)(startOff + index)] >= 0)){ + writeByte((byte)(UINT_TYPE | val[(short)(startOff + index)])); }else if (diff == 1){ writeByte((byte)(UINT_TYPE | UINT8_LENGTH)); - writeByte(val[index]); + writeByte(val[(short)(startOff + index)]); }else if(diff == 2){ writeByte((byte)(UINT_TYPE | UINT16_LENGTH)); - writeBytes(val, index, (short)2); + writeBytes(val, (short)(startOff + index), (short)2); }else if(diff <= 4){ writeByte((byte)(UINT_TYPE | UINT32_LENGTH)); - writeBytes(val, (short)(len - 4), (short)4); + writeBytes(val, (short)(startOff + len - 4), (short)4); }else { writeByte((byte)(UINT_TYPE | UINT64_LENGTH)); - writeBytes(val, (short)0, (short)8); + writeBytes(val, startOff, (short)8); } } - private void encode(KMByteBlob obj) { - writeMajorTypeWithLength(BYTES_TYPE, obj.length()); - writeBytes(obj.getVal(), obj.getStartOff(), obj.length()); + private void encodeByteBlob(short obj) { + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.cast(obj).length()); + writeBytes(KMByteBlob.cast(obj).getBuffer(), KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); } private void writeByteValue(byte val){ - if(val < UINT8_LENGTH){ + if((val < UINT8_LENGTH) && (val >=0)){ writeByte((byte)(UINT_TYPE | val)); }else{ writeByte((byte)(UINT_TYPE | UINT8_LENGTH)); - writeByte(val); + writeByte((byte)val); } } @@ -257,7 +299,6 @@ private void writeTag(short tagType, short tagKey){ writeShort(tagType); writeShort(tagKey); } - // TODO bug here private void writeMajorTypeWithLength(byte majorType, short len) { if(len <= TINY_PAYLOAD){ writeByte((byte)(majorType | (byte) (len & ADDITIONAL_MASK))); @@ -288,7 +329,7 @@ private void writeByte(byte val){ private void incrementStartOff(short inc){ startOff += inc; if (startOff >= this.length) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java index 7dad1d27..d5a69270 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -17,45 +17,65 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMEnum extends KMType { - private static short[] types = {HARDWARE_TYPE, KEY_FORMAT, KEY_DERIVATION_FUNCTION}; + private static KMEnum prototype; + private static short instPtr; + + private static short[] types = {HARDWARE_TYPE, KEY_FORMAT, KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, DEVICE_LOCKED, USER_AUTH_TYPE, PURPOSE}; private static Object[] enums = null; - private short type; - private byte val; + private KMEnum() {} - private KMEnum() { - init(); + private static KMEnum proto(short ptr) { + if (prototype == null) prototype = new KMEnum(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - type = 0; - val = 0; + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(ENUM_TYPE); } - @Override + public short length() { - return 1; + return Util.getShort(heap, (short) (instPtr + 1)); + } + + // cast the ptr to KMByteBlob + public static KMEnum cast(short ptr) { + if (heap[ptr] != ENUM_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - public static KMEnum instance() { - return repository.newEnum(); + public static short instance(short enumType) { + if (!validateEnum(enumType, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short)2); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), enumType); + return ptr; } - public static KMEnum instance(short enumType, byte val) { - KMEnum inst = repository.newEnum(); + public static short instance(short enumType, byte val) { if (!validateEnum(enumType, val)) { - throw new KMException(ISO7816.SW_DATA_INVALID); + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } - inst.type = enumType; - inst.val = val; - return inst; + short ptr = KMType.instance(ENUM_TYPE, (short)3); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), enumType); + heap[(short)(ptr+TLV_HEADER_SIZE+2)] = val; + return ptr; } - public static void create(KMEnum[] enumRefTable) { + private static void create() { if (enums == null) { enums = new Object[] { @@ -68,35 +88,36 @@ public static void create(KMEnum[] enumRefTable) { ISO18033_2_KDF1_SHA256, ISO18033_2_KDF2_SHA1, ISO18033_2_KDF2_SHA256 - } + }, + new byte[] {SELF_SIGNED_BOOT, VERIFIED_BOOT}, + new byte[] {DEVICE_LOCKED_TRUE, DEVICE_LOCKED_FALSE}, + new byte[] {USER_AUTH_NONE,PASSWORD,FINGERPRINT, BOTH}, + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY} }; } - byte index = 0; - while (index < enumRefTable.length) { - enumRefTable[index] = new KMEnum(); - index++; - } } - public KMEnum setVal(byte val) { - this.val = val; - return this; + public void setVal(byte val) { + heap[(short)(instPtr+TLV_HEADER_SIZE+2)] = val; } public byte getVal() { - return val; + return heap[(short)(instPtr+TLV_HEADER_SIZE+2)]; } - public KMEnum setType(short type) { - this.type = type; - return this; + public void setEnumType(short type) { + Util.setShort(heap, (short)(instPtr+TLV_HEADER_SIZE),type); } - public short getType() { - return type; + public short getEnumType() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE)); } - // validate enumeration keys and values. + + // isValidTag enumeration keys and values. private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; // check if key exists short index = (short) types.length; while (--index >= 0) { @@ -104,8 +125,8 @@ private static boolean validateEnum(short key, byte value) { // check if value given if (value != NO_VALUE) { // check if the value exist - byte[] vals = (byte[]) enums[index]; - short enumInd = (short) vals.length; + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; while (--enumInd >= 0) { if (vals[enumInd] == value) { // return true if value exist @@ -122,4 +143,5 @@ private static boolean validateEnum(short key, byte value) { // return false if key does not exist return false; } + } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java index 9c76ce66..6304d7a2 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -17,90 +17,119 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMEnumArrayTag extends KMTag { + private static KMEnumArrayTag prototype; + private static short instPtr; + // Arrays given below, together they form multi dimensional array. // Tag private static short[] tags = {PURPOSE, BLOCK_MODE, DIGEST, PADDING}; // Tag Values. private static Object[] enums = null; - // Tag Key - private short key; - // Byte Array of Tag Values. - private KMByteBlob array; - // assignBlob constructor - private KMEnumArrayTag() { - init(); + private KMEnumArrayTag() {} + + private static KMEnumArrayTag proto(short ptr) { + if (prototype == null) prototype = new KMEnumArrayTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - array = null; + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), INVALID_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), blobPtr); + return ptr; + } + + public static short instance(short key) { + byte[] vals = getAllowedEnumValues(key); + if (vals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short blobPtr = KMByteBlob.exp(); + return instance(key, blobPtr); + } + + public static short instance(short key, short byteBlob) { + byte[] allowedVals = getAllowedEnumValues(key); + if (allowedVals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + KMByteBlob blob = KMByteBlob.cast(byteBlob); + short byteIndex = 0; + short enumIndex; + boolean validValue; + while (byteIndex < blob.length()) { + enumIndex = 0; + validValue = false; + while (enumIndex < allowedVals.length) { + if (blob.get(byteIndex) == allowedVals[enumIndex]) { + validValue = true; + break; + } + enumIndex++; + } + if (!validValue) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byteIndex++; + } + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), byteBlob); + return ptr; + } + + public static KMEnumArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_ARRAY_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - @Override public short getKey() { - return key; + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); } - @Override public short getTagType() { return KMType.ENUM_ARRAY_TAG; } - // returns the length - @Override - public short length() { - return array.length(); + + public short getValues() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); } - public static KMEnumArrayTag instance() { - return repository.newEnumArrayTag(); + public short length() { + short blobPtr = Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); + return KMByteBlob.cast(blobPtr).length(); } - public static void create(KMEnumArrayTag[] enumArrayTagRefTable) { + public static void create() { if (enums == null) { enums = new Object[] { new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY}, - new byte[] {ECB, CBC, CTR}, + new byte[] {ECB, CBC, CTR, GCM}, new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, new byte[] { PADDING_NONE, RSA_OAEP, RSA_PSS, RSA_PKCS1_1_5_ENCRYPT, RSA_PKCS1_1_5_SIGN, PKCS7 } }; } - byte index = 0; - while (index < enumArrayTagRefTable.length) { - enumArrayTagRefTable[index] = new KMEnumArrayTag(); - index++; - } - } - - // create default assignBlob without any value array - public static KMEnumArrayTag instance(short key) { - // check if key is valid. - byte[] vals = getAllowedEnumValues(key); - if (vals == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMEnumArrayTag tag = repository.newEnumArrayTag(); - tag.key = key; - tag.array = null; - return tag; } - // Set the expected length for the prototype. - public KMEnumArrayTag withLength(short length) { - array.withLength(length); - return this; - } - - // get the allowed enum values for given tag key private static byte[] getAllowedEnumValues(short key) { - // check if key is allowed + create(); short index = (short) tags.length; while (--index >= 0) { if (tags[index] == key) { @@ -110,41 +139,143 @@ private static byte[] getAllowedEnumValues(short key) { return null; } - // get value array of this tag assignBlob. - public KMByteBlob getValues() { - return this.array; + public static short getValues(short tagId, short params, byte[] buf, short start) { + short tag = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag == KMType.INVALID_VALUE) { + return KMType.INVALID_VALUE; + } + tag = KMEnumArrayTag.cast(tag).getValues(); + return KMByteBlob.cast(tag).getValues(buf, start); + } + + public short get(short index){ + return KMByteBlob.cast(getValues()).get(index); } - public KMEnumArrayTag setValues(KMByteBlob val) { - this.array = val; - return this; + public static boolean contains(short tagId, short tagValue, short params) { + short tag = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + while (index < KMEnumArrayTag.cast(tag).length()) { + if (tagValue == KMEnumArrayTag.cast(tag).get(index)) { + return true; + } + index++; + } + } + return false; } - // instantiate enum array pointing to existing array. - public static KMEnumArrayTag instance(short key, KMByteBlob blob) { - // validate key - byte[] allowedVals = getAllowedEnumValues(key); - if (allowedVals == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); + public static short length(short tagId, short params) { + short tag = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + return KMEnumArrayTag.cast(tag).length(); } - short byteIndex = 0; - while (byteIndex < blob.length()) { - short enumIndex = 0; - boolean validValue = false; - while (enumIndex < allowedVals.length) { - if (blob.get(byteIndex) == allowedVals[enumIndex]) { - validValue = true; + return KMType.INVALID_VALUE; + } + public boolean contains(short tagValue){ + short index = 0; + while(index < length()){ + if(get(index) == (byte)tagValue){ + return true; + } + index++; + } + return false; + } + + public boolean isValidDigests(byte alg){ + short index = 0; + short digest; + while(index < length()){ + digest = get(index); + switch (alg) { + case KMType.EC: + case KMType.RSA: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) return false; break; - } - enumIndex++; + case KMType.HMAC: + if (digest != KMType.SHA2_256) return false; + break; + case KMType.AES: + case KMType.DES: + if (digest != KMType.DIGEST_NONE) return false; + break; + default: + return false; } - if (!validValue) { - throw new KMException(ISO7816.SW_DATA_INVALID); + index++; + } + return true; + } + + public boolean isValidPaddingModes(byte alg){ + short index = 0; + short padding; + while(index < length()){ + padding = get(index); + switch(alg){ + case KMType.RSA: + if(padding != KMType.RSA_OAEP && padding != KMType.PADDING_NONE && + padding != KMType.RSA_PKCS1_1_5_SIGN && padding != KMType.RSA_PKCS1_1_5_ENCRYPT && + padding != KMType.RSA_PSS){ + return false; + } + break; + case KMType.AES: + case KMType.DES: + if(padding != KMType.PKCS7 && padding != KMType.PADDING_NONE){ + return false; + } + break; + case KMType.EC: + case KMType.HMAC: + if(padding != PADDING_NONE){ + return false; + } + break; + default: + return false; } - byteIndex++; + index++; + } + return true; + } + public boolean isValidPurpose(byte alg){ + short index = 0; + short purpose; + while(index < length()){ + purpose = get(index); + switch(purpose){ + case KMType.DECRYPT: + case KMType.ENCRYPT: + if(alg != KMType.RSA && alg != KMType.AES && + alg != KMType.DES){ + return false; + } + break; + case KMType.SIGN: + case KMType.VERIFY: + if(alg != KMType.HMAC && alg != KMType.RSA && + alg != KMType.EC){ + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidBlockMode(byte alg) { + if (alg == KMType.AES || alg == KMType.DES) { + return true; + } else { + return false; } - KMEnumArrayTag tag = repository.newEnumArrayTag(); - tag.key = key; - tag.array = blob; - return tag; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java index 3cfa840e..b75563d6 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -17,8 +17,12 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMEnumTag extends KMTag { + private static KMEnumTag prototype; + private static short instPtr; private static short[] tags = { ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE @@ -26,70 +30,81 @@ public class KMEnumTag extends KMTag { private static Object[] enums = null; - private short key; - private byte val; + private KMEnumTag() {} - // assignBlob constructor - private KMEnumTag() { - init(); + private static KMEnumTag proto(short ptr) { + if (prototype == null) prototype = new KMEnumTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - val = 0; + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short)2); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), ENUM_TAG); + return ptr; } - @Override - public short getKey() { - return key; + public static short instance(short key) { + if(!validateEnum(key, NO_VALUE)){ + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short)4); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + return ptr; } - @Override - public short length() { - return 1; + public static short instance(short key, byte val) { + if(!validateEnum(key, val)){ + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short)5); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + heap[(short)(ptr+TLV_HEADER_SIZE+4)]= val; + return ptr; } - @Override - public short getTagType() { - return KMType.ENUM_TAG; + public static KMEnumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - public static KMEnumTag instance() { - return repository.newEnumTag(); + public short getKey() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); } - public static KMEnumTag instance(short key) { - if (validateEnum(key, NO_VALUE)) { - KMEnumTag tag = repository.newEnumTag(); - tag.key = key; - return tag; - } else { - throw new KMException(ISO7816.SW_DATA_INVALID); - } + public short getTagType() { + return KMType.ENUM_TAG; + } + + public byte getValue() { + return heap[(short)(instPtr+TLV_HEADER_SIZE+4)]; } - public static void create(KMEnumTag[] enumTagRefTable) { + public static void create() { if (enums == null) { enums = new Object[] { new byte[] {RSA, DES, EC, AES, HMAC}, new byte[] {P_224, P_256, P_384, P_521}, new byte[] {STANDALONE, REQUIRES_FILE_SYSTEM}, - new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, ANY}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, (byte)(PASSWORD & FINGERPRINT),ANY}, new byte[] {GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} }; } - byte index = 0; - while (index < enumTagRefTable.length) { - enumTagRefTable[index] = new KMEnumTag(); - index++; - } } - // validate enumeration keys and values. + // isValidTag enumeration keys and values. private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; // check if key exists short index = (short) tags.length; while (--index >= 0) { @@ -97,8 +112,8 @@ private static boolean validateEnum(short key, byte value) { // check if value given if (value != NO_VALUE) { // check if the value exist - byte[] vals = (byte[]) enums[index]; - short enumInd = (short) vals.length; + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; while (--enumInd >= 0) { if (vals[enumInd] == value) { // return true if value exist @@ -116,18 +131,11 @@ private static boolean validateEnum(short key, byte value) { return false; } - // get value of this tag assignBlob. - public byte getValue() { - return val; - } - - // instantiate enum tag. - public static KMEnumTag instance(short key, byte val) { - if (!validateEnum(key, val)) { - throw new KMException(ISO7816.SW_DATA_INVALID); + public static short getValue(short tagType, short keyParameters){ + short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagType, keyParameters); + if(tagPtr != KMType.INVALID_VALUE){ + return heap[(short)(tagPtr+TLV_HEADER_SIZE+4)]; } - KMEnumTag tag = instance(key); - tag.val = val; - return tag; + return KMType.INVALID_VALUE; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMError.java b/Applet/Applet/src/com/android/javacard/keymaster/KMError.java new file mode 100644 index 00000000..25557d27 --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMError.java @@ -0,0 +1,78 @@ +package com.android.javacard.keymaster; + +public class KMError { + public static short OK = 0 ; + public static short ROOT_OF_TRUST_ALREADY_SET = 1; + public static short UNSUPPORTED_PURPOSE = 2; + public static short INCOMPATIBLE_PURPOSE = 3; + public static short UNSUPPORTED_ALGORITHM = 4; + public static short INCOMPATIBLE_ALGORITHM = 5; + public static short UNSUPPORTED_KEY_SIZE = 6; + public static short UNSUPPORTED_BLOCK_MODE = 7; + public static short INCOMPATIBLE_BLOCK_MODE = 8; + public static short UNSUPPORTED_MAC_LENGTH = 9; + public static short UNSUPPORTED_PADDING_MODE = 10; + public static short INCOMPATIBLE_PADDING_MODE = 11; + public static short UNSUPPORTED_DIGEST = 12; + public static short INCOMPATIBLE_DIGEST = 13; + public static short INVALID_EXPIRATION_TIME = 14; + public static short INVALID_USER_ID = 15; + public static short INVALID_AUTHORIZATION_TIMEOUT = 16; + public static short UNSUPPORTED_KEY_FORMAT = 17; + public static short INCOMPATIBLE_KEY_FORMAT = 18; + public static short UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = 19; /** For PKCS8 & PKCS12 */ + public static short UNSUPPORTED_KEY_VERIFICATION_ALGORITHM = 20; /** For PKCS8 & PKCS12 */ + public static short INVALID_INPUT_LENGTH = 21; + public static short KEY_EXPORT_OPTIONS_INVALID = 22; + public static short DELEGATION_NOT_ALLOWED = 23; + public static short KEY_NOT_YET_VALID = 24; + public static short KEY_EXPIRED = 25; + public static short KEY_USER_NOT_AUTHENTICATED = 26; + public static short OUTPUT_PARAMETER_NULL = 27; + public static short INVALID_OPERATION_HANDLE = 28; + public static short INSUFFICIENT_BUFFER_SPACE = 29; + public static short VERIFICATION_FAILED = 30; + public static short TOO_MANY_OPERATIONS = 31; + public static short UNEXPECTED_NULL_POINTER = 32; + public static short INVALID_KEY_BLOB = 33; + public static short IMPORTED_KEY_NOT_ENCRYPTED = 34; + public static short IMPORTED_KEY_DECRYPTION_FAILED = 35; + public static short IMPORTED_KEY_NOT_SIGNED = 36; + public static short IMPORTED_KEY_VERIFICATION_FAILED = 37; + public static short INVALID_ARGUMENT = 38; + public static short UNSUPPORTED_TAG = 39; + public static short INVALID_TAG = 40; + public static short MEMORY_ALLOCATION_FAILED = 41; + public static short IMPORT_PARAMETER_MISMATCH = 44; + public static short SECURE_HW_ACCESS_DENIED = 45; + public static short OPERATION_CANCELLED = 46; + public static short CONCURRENT_ACCESS_CONFLICT = 47; + public static short SECURE_HW_BUSY = 48; + public static short SECURE_HW_COMMUNICATION_FAILED = 49; + public static short UNSUPPORTED_EC_FIELD = 50; + public static short MISSING_NONCE = 51; + public static short INVALID_NONCE = 52; + public static short MISSING_MAC_LENGTH = 53; + public static short KEY_RATE_LIMIT_EXCEEDED = 54; + public static short CALLER_NONCE_PROHIBITED = 55; + public static short KEY_MAX_OPS_EXCEEDED = 56; + public static short INVALID_MAC_LENGTH = 57; + public static short MISSING_MIN_MAC_LENGTH = 58; + public static short UNSUPPORTED_MIN_MAC_LENGTH = 59; + public static short UNSUPPORTED_KDF = 60; + public static short UNSUPPORTED_EC_CURVE = 61; + public static short KEY_REQUIRES_UPGRADE = 62; + public static short ATTESTATION_CHALLENGE_MISSING = 63; + public static short KEYMASTER_NOT_CONFIGURED = 64; + public static short ATTESTATION_APPLICATION_ID_MISSING = 65; + public static short CANNOT_ATTEST_IDS = 66; + public static short ROLLBACK_RESISTANCE_UNAVAILABLE = 67; + public static short HARDWARE_TYPE_UNAVAILABLE = 68; + public static short PROOF_OF_PRESENCE_REQUIRED = 69; + public static short CONCURRENT_PROOF_OF_PRESENCE_REQUESTED = 70; + public static short NO_USER_CONFIRMATION = 71; + public static short DEVICE_LOCKED = 72; + public static short UNIMPLEMENTED = 100; + public static short VERSION_MISMATCH = 101; + public static short UNKNOWN_ERROR = 1000; +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMException.java b/Applet/Applet/src/com/android/javacard/keymaster/KMException.java index 3e357613..1805a911 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMException.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMException.java @@ -16,14 +16,22 @@ package com.android.javacard.keymaster; -import javacard.framework.ISOException; - -public class KMException extends ISOException { +public class KMException extends RuntimeException { + public static short reason; + public static KMException exception; + private KMException(){ + } + public static void throwIt(short reason){ + KMException.reason = reason; + throw instance(); + } + public static KMException instance(){ + if(exception == null ) exception = new KMException(); + return exception; + } - // The Applet is not in a correct state in order to execute the command. - public static final short CMD_NOT_ACCEPTED_WRONG_STATE = (short) 0x6901; - public KMException(short i) { - super(i); + public void clear(){ + reason = KMError.UNKNOWN_ERROR; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMExportKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMExportKeyCmd.java deleted file mode 100644 index 05d28ef6..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMExportKeyCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMExportKeyCmd extends KMAbstractCmd { - public static final byte INS_EXPORT_KEY_CMD = 0x13; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_EXPORT_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMFinishOperationCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMFinishOperationCmd.java deleted file mode 100644 index 0267e4fb..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMFinishOperationCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMFinishOperationCmd extends KMAbstractCmd { - public static final byte INS_FINISH_OPERATION_CMD = 0x21; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_FINISH_OPERATION_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMGenerateKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMGenerateKeyCmd.java deleted file mode 100644 index 0c2d69d1..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMGenerateKeyCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMGenerateKeyCmd extends KMAbstractCmd { - public static final byte INS_GENERATE_KEY_CMD = 0x10; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_GENERATE_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMGetHWInfoCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMGetHWInfoCmd.java deleted file mode 100644 index 3f6cb8f8..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMGetHWInfoCmd.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMGetHWInfoCmd extends KMAbstractCmd { - public static final byte INS_GET_HW_INFO_CMD = 0x1E; - public static final byte[] JavacardKeymasterDevice = { - 0x4A, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, - 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - }; - public static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return KMArray.instance((short) 3) - .add((short) 0, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)) - .add( - (short) 1, - KMByteBlob.instance( - JavacardKeymasterDevice, (short) 0, (short) JavacardKeymasterDevice.length)) - .add((short) 2, KMByteBlob.instance(Google, (short) 0, (short) Google.length)); - } - - @Override - public byte getIns() { - return INS_GET_HW_INFO_CMD; - } - - @Override - public boolean hasArguments() { - return false; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMGetHmacSharingParametersCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMGetHmacSharingParametersCmd.java deleted file mode 100644 index 19165531..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMGetHmacSharingParametersCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMGetHmacSharingParametersCmd extends KMAbstractCmd { - public static final byte INS_GET_HMAC_SHARING_PARAM_CMD = 0x1C; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_GET_HMAC_SHARING_PARAM_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMGetKeyCharacteristicsCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMGetKeyCharacteristicsCmd.java deleted file mode 100644 index 20fef22b..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMGetKeyCharacteristicsCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMGetKeyCharacteristicsCmd extends KMAbstractCmd { - public static final byte INS_GET_KEY_CHARACTERISTICS_CMD = 0x1D; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_GET_KEY_CHARACTERISTICS_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java index ca3c2804..96b1ed9d 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -17,6 +17,8 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMHardwareAuthToken extends KMType { public static final byte CHALLENGE = 0x00; @@ -26,76 +28,128 @@ public class KMHardwareAuthToken extends KMType { public static final byte TIMESTAMP = 0x04; public static final byte MAC = 0x05; - private KMArray vals; + private static KMHardwareAuthToken prototype; + private static short instPtr; + + private KMHardwareAuthToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short)6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(USER_ID, KMInteger.exp()); + arr.add(AUTHENTICATOR_ID, KMInteger.exp()); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHardwareAuthToken proto(short ptr) { + if (prototype == null) prototype = new KMHardwareAuthToken(); + instPtr = ptr; + return prototype; + } - private KMHardwareAuthToken() { - init(); + public static short instance() { + short arrPtr = KMArray.instance((short)6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short)0)); + arr.add(USER_ID, KMInteger.uint_16((short)0)); + arr.add(AUTHENTICATOR_ID, KMInteger.uint_16((short)0)); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + arr.add(TIMESTAMP, KMInteger.uint_16((short)0)); + arr.add(MAC, KMByteBlob.instance((short)0)); + return instance(arrPtr); } - @Override - public void init() { - vals = null; + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if(arr.length() != 6)ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short)2); + Util.setShort(heap, (short)(ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHardwareAuthToken cast(short ptr) { + if (heap[ptr] != HW_AUTH_TOKEN_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if(heap[arrPtr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); + } + + public short getVals() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - @Override public short length() { - return vals.length(); + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); } - public static KMHardwareAuthToken instance() { - KMHardwareAuthToken inst = repository.newHwAuthToken(); - inst.vals = KMArray.instance((short) 6); - inst.vals.add(CHALLENGE, KMInteger.instance()); - inst.vals.add(USER_ID, KMInteger.instance()); - inst.vals.add(AUTHENTICATOR_ID, KMInteger.instance()); - inst.vals.add(HW_AUTHENTICATOR_TYPE, KMEnumTag.instance(KMType.USER_AUTH_TYPE)); - inst.vals.add(TIMESTAMP, KMInteger.instance()); - inst.vals.add(MAC, KMByteBlob.instance()); - return inst; + public short getUserId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(USER_ID); } - public static KMHardwareAuthToken instance(KMArray vals) { - if (vals.length() != 6) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMHardwareAuthToken inst = repository.newHwAuthToken(); - inst.vals = vals; - return inst; + public void setUserId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(USER_ID, vals); } - public static void create(KMHardwareAuthToken[] hwAuthTokenRefTable) { - byte index = 0; - while (index < hwAuthTokenRefTable.length) { - hwAuthTokenRefTable[index] = new KMHardwareAuthToken(); - index++; - } + public short getAuthenticatorId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); } - public KMInteger getChallenge() { - return (KMInteger) vals.get(CHALLENGE); + public void setAuthenticatorId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); } - public KMInteger getUserId() { - return (KMInteger) vals.get(USER_ID); + public short getHwAuthenticatorType() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); } - public KMInteger getAuthenticatorId() { - return (KMInteger) vals.get(AUTHENTICATOR_ID); + public void setHwAuthenticatorType(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); } - public byte getHwAuthenticatorType() { - return ((KMEnumTag) vals.get(HW_AUTHENTICATOR_TYPE)).getValue(); + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); } - public KMInteger getTimestamp() { - return (KMInteger) vals.get(TIMESTAMP); + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); } - public KMByteBlob getMac() { - return (KMByteBlob) vals.get(MAC); + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); } - public KMArray getVals() { - return vals; + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java index ef08696b..bc34c217 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -17,60 +17,79 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMHmacSharingParameters extends KMType { public static final byte SEED = 0x00; public static final byte NONCE = 0x01; - private KMArray vals; - private KMHmacSharingParameters() { - init(); + private static KMHmacSharingParameters prototype; + private static short instPtr; + + private KMHmacSharingParameters() {} + + public static short exp() { + short arrPtr = KMArray.instance((short)2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SEED, KMByteBlob.exp()); + arr.add(NONCE, KMByteBlob.exp()); + return instance(arrPtr); } - @Override - public void init() { - vals = null; + private static KMHmacSharingParameters proto(short ptr) { + if (prototype == null) prototype = new KMHmacSharingParameters(); + instPtr = ptr; + return prototype; } - @Override - public short length() { - return vals.length(); + public static short instance() { + short arrPtr = KMArray.instance((short)2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short)2); + if(KMArray.cast(vals).length() != 2) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + Util.setShort(heap, (short)(ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHmacSharingParameters cast(short ptr) { + if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if(heap[arrPtr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); } - public static KMHmacSharingParameters instance() { - KMHmacSharingParameters inst = repository.newHmacSharingParameters(); - inst.vals = KMArray.instance((short) 2); - inst.vals.add(SEED, KMByteBlob.instance()); - inst.vals.add(NONCE, KMByteBlob.instance()); - return inst; + public short getVals() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - public static KMHmacSharingParameters instance(KMArray vals) { - if (vals.length() != 2) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMHmacSharingParameters inst = repository.newHmacSharingParameters(); - inst.vals = vals; - return inst; + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); } - public static void create(KMHmacSharingParameters[] hmacSharingParamsRefTable) { - byte index = 0; - while (index < hmacSharingParamsRefTable.length) { - hmacSharingParamsRefTable[index] = new KMHmacSharingParameters(); - index++; - } + public void setSeed(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SEED, vals); } - public KMByteBlob getSeed() { - return (KMByteBlob) vals.get(SEED); + public void setNonce(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(NONCE, vals); } - public KMByteBlob getNonce() { - return (KMByteBlob) vals.get(NONCE); + public short getNonce() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(NONCE); } - public KMArray getVals() { - return vals; + public short getSeed() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SEED); } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMImportKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMImportKeyCmd.java deleted file mode 100644 index 6be0d0d7..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMImportKeyCmd.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMImportKeyCmd extends KMAbstractCmd { - public static final byte INS_IMPORT_KEY_CMD = 0x11; - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_IMPORT_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMImportWrappedKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMImportWrappedKeyCmd.java deleted file mode 100644 index e9896748..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMImportWrappedKeyCmd.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMImportWrappedKeyCmd extends KMAbstractCmd { - public static final byte INS_IMPORT_WRAPPED_KEY_CMD = 0x12; - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_IMPORT_WRAPPED_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java b/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java index 19996d18..4d7e33b1 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -17,116 +17,150 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; // Represents 8 bit, 16 bit, 32 bit and 64 bit integers public class KMInteger extends KMType { - private byte[] val; + public static final short UINT_32 = 4; + public static final short UINT_64 = 8; + private static KMInteger prototype; + private static short instPtr; - private KMInteger() { - init(); + private KMInteger() {} + + private static KMInteger proto(short ptr) { + if (prototype == null) prototype = new KMInteger(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - val = null; + public static short exp() { + return KMType.exp(INTEGER_TYPE); } - @Override - public short length() { - return (short) this.val.length; + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + if (length > 4) { + length = UINT_64; + } else { + length = UINT_32; + } + return KMType.instance(INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } } - public static KMInteger instance() { - return repository.newInteger(); + public static KMInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != INTEGER_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } // create integer and copy byte value - public static KMInteger uint_8(byte num) { - KMInteger inst = repository.newInteger(); - inst.val = repository.newIntegerArray((short) 4); - inst.val[3] = num; - return inst; + public static short uint_8(byte num) { + short ptr = instance(UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; } // create integer and copy short value - public static KMInteger uint_16(short num) { - KMInteger inst = repository.newInteger(); - inst.val = repository.newIntegerArray((short) 4); - inst.val[2] = (byte) ((num >> 8) & 0xff); - inst.val[3] = (byte) (num & 0xff); - return inst; + public static short uint_16(short num) { + short ptr = instance(UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; } // create integer and copy integer value - public static KMInteger uint_32(byte[] num, short offset) { - KMInteger inst = repository.newInteger(); - inst.val = repository.newIntegerArray((short) 4); - Util.arrayCopy(num, offset, inst.val, (short) 0, (short) 4); - return inst; + public static short uint_32(byte[] num, short offset) { + short ptr = instance(UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32); + return ptr; } // create integer and copy integer value - public static KMInteger uint_64(byte[] num, short offset) { - KMInteger inst = repository.newInteger(); - inst.val = repository.newIntegerArray((short) 8); - Util.arrayCopy(num, offset, inst.val, (short) 0, (short) 8); - return inst; - } - - public static void create(KMInteger[] integerRefTable) { - byte index = 0; - while (index < integerRefTable.length) { - integerRefTable[index] = new KMInteger(); - index++; - } + public static short uint_64(byte[] num, short offset) { + short ptr = instance(UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64); + return ptr; + } + + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (instPtr + 1)); } - public byte[] getValue() { - return val; + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; } - public KMInteger setValue(short val) { - this.val[2] = (byte) (val >> 8); - this.val[3] = (byte) (val & 0xFF); - return this; + // Get the start of value + public short getStartOff() { + return (short) (instPtr + TLV_HEADER_SIZE); } - public KMInteger setValue(byte[] val) { - this.val = val; - return this; + public void getValue(byte[] dest, short destOff, short length){ + Util.arrayCopyNonAtomic(heap, (short)(instPtr+TLV_HEADER_SIZE), dest, destOff, length); + } + public void setValue(byte[] src, short srcOff){ + Util.arrayCopyNonAtomic(src, srcOff, heap, (short)(instPtr+TLV_HEADER_SIZE), length()); + } + public short value(byte[] dest, short destOff){ + Util.arrayCopyNonAtomic(heap, (short)(instPtr+TLV_HEADER_SIZE), dest, destOff, length()); + return length(); } public short getShort() { - if (val == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } else if (val.length != 4) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - return Util.makeShort(val[2], val[3]); + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE + 2)); + } + public short getSignificantShort(){ + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - public byte getByte() { - if (val == null) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } else if (val.length != 4) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - return val[3]; + return heap[(short) (instPtr + TLV_HEADER_SIZE + 3)]; } - // copy the integer value from bytes - public static KMInteger instance(byte[] num, short srcOff, short length) { - if (length == 1) { - return uint_8(num[srcOff]); - } else if (length == 2) { - return uint_16(Util.makeShort(num[srcOff], num[(short) (srcOff + 1)])); - } else if (length == 4) { - return uint_32(num, srcOff); - } else if (length == 8) { - return uint_64(num, srcOff); - } else { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + public boolean isZero() { + if(getShort() == 0 && getSignificantShort() == 0){ + return true; } + return false; + } + + public static short compare(short num1, short num2){ + short num1Buf = repository.alloc((short)8); + short num2Buf = repository.alloc((short)8); + Util.arrayFillNonAtomic(repository.getHeap(),num1Buf,(short)8,(byte)0); + Util.arrayFillNonAtomic(repository.getHeap(),num2Buf,(short)8,(byte)0); + short numPtr = KMInteger.cast(num1).getStartOff(); + short len = KMInteger.cast(num1).length(); + KMInteger.cast(num1).getValue(repository.getHeap(),(short)(num1Buf+(short)(8-len)),len); + numPtr = KMInteger.cast(num2).getStartOff(); + len = KMInteger.cast(num2).length(); + KMInteger.cast(num2).getValue(repository.getHeap(),(short)(num2Buf+(short)(8-len)),len); + return Util.arrayCompare( + repository.getHeap(), num1Buf, + repository.getHeap(), num2Buf, + (short)8); } + } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java index 9c9258a2..c154f5a0 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -17,77 +17,97 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMIntegerArrayTag extends KMTag { + private static KMIntegerArrayTag prototype; + private static short instPtr; + private static final short[] tags = {USER_SECURE_ID}; - private short key; - private KMVector vals; - private short tagType; - private KMIntegerArrayTag() { - init(); + private KMIntegerArrayTag() {} + + private static KMIntegerArrayTag proto(short ptr) { + if (prototype == null) prototype = new KMIntegerArrayTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - vals = null; - tagType = KMType.UINT_ARRAY_TAG; + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(KMType.INTEGER_TYPE); + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), INVALID_TAG); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), arrPtr); + return ptr; } - @Override - public short getKey() { - return key; + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(); + return instance(tagType, key, arrPtr); } - @Override - public short length() { - return this.vals.length(); + public static short instance(short tagType, short key, short arrObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if(heap[arrObj] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short)6); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+2), key); + Util.setShort(heap, (short)(ptr+TLV_HEADER_SIZE+4), arrObj); + return ptr; + } + + public static KMIntegerArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - @Override public short getTagType() { - return tagType; + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE)); } - public static KMIntegerArrayTag instance() { - return repository.newIntegerArrayTag(); + public short getKey() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); } - public static void create(KMIntegerArrayTag[] intArrayTagRefTable) { - byte index = 0; - while (index < intArrayTagRefTable.length) { - intArrayTagRefTable[index] = new KMIntegerArrayTag(); - index++; - } + public short getValues() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); } - public KMIntegerArrayTag asUlongArray() { - tagType = KMType.ULONG_ARRAY_TAG; - return this; + public short length() { + short ptr = getValues(); + return KMArray.cast(ptr).length(); } - public static KMIntegerArrayTag instance(short key) { - if (!validateKey(key)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMIntegerArrayTag tag = repository.newIntegerArrayTag(); - tag.key = key; - tag.vals = KMVector.instance(KMInteger.instance()); - return tag; + public void add(short index, short val) { + KMArray arr = KMArray.cast(getValues()); + arr.add(index, val); } - public static KMIntegerArrayTag instance(short key, KMVector val) { - if (!(val.getType() instanceof KMInteger)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - if (!(validateKey(key))) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMIntegerArrayTag tag = repository.newIntegerArrayTag(); - tag.key = key; - tag.vals = val; - return tag; + public short get(short index) { + KMArray arr = KMArray.cast(getValues()); + return arr.get(index); } private static boolean validateKey(short key) { @@ -100,25 +120,28 @@ private static boolean validateKey(short key) { return false; } - public KMIntegerArrayTag withLength(short length) { - this.vals.withLength(length); - return this; - } - - public KMVector getValues() { - return this.vals; - } - - public KMIntegerArrayTag setValues(KMVector vals) { - this.vals = vals; - return this; + // TODO this should be combined with validateKey to actually isValidTag {tagType, tagKey} pair. + private static boolean validateTagType(short tagType) { + if ((tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG)) { + return true; + } + return false; } - public void add(short index, KMInteger val) { - this.vals.add(index, val); + public static boolean contains(short tagId, short tagValue, short params) { + short tag = + KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + tag = KMIntegerArrayTag.cast(tag).getValues(); + while (index < KMArray.cast(tag).length()) { + if (KMInteger.compare(tagValue, KMArray.cast(tag).get(index)) == 0) { + return true; + } + index++; + } + } + return false; } - public KMInteger get(short index) { - return (KMInteger) this.vals.get(index); - } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java index 00dbf256..e3736793 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -17,9 +17,14 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; // Implements UINT, ULONG and DATE tags. public class KMIntegerTag extends KMTag { + private static KMIntegerTag prototype; + private static short instPtr; + private static final short[] tags = { // UINT KEYSIZE, @@ -42,65 +47,78 @@ public class KMIntegerTag extends KMTag { CREATION_DATETIME }; - private short key; - private KMInteger val; - private short tagType; + private KMIntegerTag() {} - private KMIntegerTag() { - init(); + private static KMIntegerTag proto(short ptr) { + if (prototype == null) prototype = new KMIntegerTag(); + instPtr = ptr; + return prototype; } - @Override - public void init() { - key = 0; - val = null; - tagType = KMType.UINT_TAG; + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intPtr); + return ptr; } - @Override - public short getKey() { - return key; + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + return instance(tagType, key, intPtr); } - @Override - public short length() { - return (short) val.getValue().length; + public static short instance(short tagType, short key, short intObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[intObj] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intObj); + return ptr; } - @Override - public short getTagType() { - return tagType; + public static KMIntegerTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); } - public static KMIntegerTag instance() { - return repository.newIntegerTag(); + public short getTagType() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - public static KMIntegerTag instance(short key) { - if (!validateKey(key)) { - throw new KMException(ISO7816.SW_DATA_INVALID); - } - KMIntegerTag tag = repository.newIntegerTag(); - tag.key = key; - tag.val = null; - return tag; + public short getKey() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE + 2)); } - public static KMIntegerTag instance(short givenKey, KMInteger val) { - KMIntegerTag tag = KMIntegerTag.instance(givenKey); - tag.val = val; - if (val.length() == 8) { - tag.tagType = KMType.ULONG_TAG; - } - return tag; + public short getValue() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE + 4)); } - public static void create(KMIntegerTag[] intTagRefTable) { - byte index = 0; - while (index < intTagRefTable.length) { - intTagRefTable[index] = new KMIntegerTag(); - index++; - } + public short length() { + KMInteger obj = KMInteger.cast(getValue()); + return obj.length(); } private static boolean validateKey(short key) { @@ -113,22 +131,66 @@ private static boolean validateKey(short key) { return false; } - public KMInteger getValue() { - return this.val; + // TODO this should be combined with validateKey to actually isValidTag {tagType, tagKey} pair. + private static boolean validateTagType(short tagType) { + if ((tagType == DATE_TAG) || (tagType == UINT_TAG) || (tagType == ULONG_TAG)) { + return true; + } + return false; } - public KMIntegerTag setValue(KMInteger val) { - this.val = val; - return this; + public static short getShortValue(short tagType, short tagKey, short keyParameters) { + short ptr; + if (tagType == UINT_TAG) { + ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + if (KMInteger.cast(ptr).getSignificantShort() == 0) { + return KMInteger.cast(ptr).getShort(); + } + } + } + return KMType.INVALID_VALUE; } - public KMIntegerTag asULong() { - tagType = KMType.ULONG_TAG; - return this; + public static short getValue( + byte[] buf, short offset, short tagType, short tagKey, short keyParameters) { + short ptr; + if ((tagType == UINT_TAG) || (tagType == ULONG_TAG) || (tagType == DATE_TAG)) { + ptr = KMKeyParameters.findTag(tagType, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + return KMInteger.cast(ptr).value(buf, offset); + } + } + return KMType.INVALID_VALUE; } - public KMIntegerTag asDate() { - tagType = KMType.DATE_TAG; - return this; + public boolean isValidKeySize(byte alg) { + short val = KMIntegerTag.cast(instPtr).getValue(); + if (KMInteger.cast(val).getSignificantShort() != 0) { + return false; + } + val = KMInteger.cast(val).getShort(); + switch (alg) { + case KMType.RSA: + if (val == 2048) return true; + break; + case KMType.AES: + if (val == 128 || val == 256) return true; + break; + case KMType.DES: + if (val == 192 || val == 168) return true; + break; + case KMType.EC: + if (val == 256) return true; + break; + case KMType.HMAC: + if (val % 8 == 0 && val >= 64 && val <= 512) return true; + break; + default: + break; + } + return false; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java index a6b5c7fe..0421e6da 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -17,60 +17,80 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMKeyCharacteristics extends KMType { public static final byte SOFTWARE_ENFORCED = 0x00; public static final byte HARDWARE_ENFORCED = 0x01; - private KMArray vals; + private static KMKeyCharacteristics prototype; + private static short instPtr; - private KMKeyCharacteristics() { - init(); + private KMKeyCharacteristics() {} + + public static short exp() { + short softEnf = KMKeyParameters.exp(); + short hwEnf = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short)2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SOFTWARE_ENFORCED, softEnf); + arr.add(HARDWARE_ENFORCED, hwEnf); + return instance(arrPtr); } - @Override - public void init() { - vals = null; + private static KMKeyCharacteristics proto(short ptr) { + if (prototype == null) prototype = new KMKeyCharacteristics(); + instPtr = ptr; + return prototype; } - @Override - public short length() { - return vals.length(); + public static short instance() { + short arrPtr = KMArray.instance((short)2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_CHAR_TYPE, (short)2); + if(KMArray.cast(vals).length() != 2) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + Util.setShort(heap, (short)(ptr + TLV_HEADER_SIZE), vals); + return ptr; } - public static KMKeyCharacteristics instance() { - KMKeyCharacteristics inst = repository.newKeyCharacteristics(); - inst.vals = KMArray.instance((short) 2); - inst.vals.add(SOFTWARE_ENFORCED, KMKeyParameters.instance()); - inst.vals.add(HARDWARE_ENFORCED, KMKeyParameters.instance()); - return inst; + public static KMKeyCharacteristics cast(short ptr) { + if (heap[ptr] != KEY_CHAR_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if(heap[arrPtr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); } - public static KMKeyCharacteristics instance(KMArray vals) { - if (vals.length() != 2) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMKeyCharacteristics inst = repository.newKeyCharacteristics(); - inst.vals = vals; - return inst; + public short getVals() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); } - public static void create(KMKeyCharacteristics[] keyCharRefTable) { - byte index = 0; - while (index < keyCharRefTable.length) { - keyCharRefTable[index] = new KMKeyCharacteristics(); - index++; - } + public short getSoftwareEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SOFTWARE_ENFORCED); } - public KMKeyParameters getSoftwareEnforced() { - return (KMKeyParameters) vals.get(SOFTWARE_ENFORCED); + public short getHardwareEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HARDWARE_ENFORCED); } - public KMKeyParameters getHardwareEnforced() { - return (KMKeyParameters) vals.get(HARDWARE_ENFORCED); + public void setSoftwareEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SOFTWARE_ENFORCED, ptr); } - public KMArray getVals() { - return vals; + public void setHardwareEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HARDWARE_ENFORCED, ptr); } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java index 44e25e98..69164fce 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -16,53 +16,378 @@ package com.android.javacard.keymaster; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + public class KMKeyParameters extends KMType { - private KMArray vals; - private KMKeyParameters() { - init(); + private static final short[] swEnforcedTags = {}; + + private static final short[] ignoredTags = { + KMType.ROOT_OF_TRUST, + KMType.RESET_SINCE_ID_ROTATION, + KMType.ALLOW_WHILE_ON_BODY, + KMType.ATTESTATION_CHALLENGE, + KMType.OS_VERSION, // not in hidden + KMType.OS_PATCH_LEVEL // not in hidden + // ALL_APPLICATIONS missing from types.hal + }; + + private static KMKeyParameters prototype; + private static short instPtr; + + private KMKeyParameters() {} + + private static KMKeyParameters proto(short ptr) { + if (prototype == null) prototype = new KMKeyParameters(); + instPtr = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 9); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 1, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 2, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 3, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 4, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 5, KMEnumTag.exp()); + arr.add((short) 6, KMEnumArrayTag.exp()); + arr.add((short) 7, KMByteTag.exp()); + arr.add((short) 8, KMBoolTag.exp()); + return instance(arrPtr); } - @Override - public void init() { - vals = null; + public static short instance(short vals) { + short ptr = KMType.instance(KEY_PARAM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyParameters cast(short ptr) { + if (heap[ptr] != KEY_PARAM_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); + } + + public short getVals() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - @Override public short length() { - return vals.length(); + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); } - public static KMKeyParameters instance() { - KMKeyParameters inst = repository.newKeyParameters(); - inst.vals = KMArray.instance((short) 9); - inst.vals.add((short) 0, KMIntegerTag.instance()); - inst.vals.add((short) 1, KMIntegerArrayTag.instance()); - inst.vals.add((short) 2, KMIntegerTag.instance().asULong()); - inst.vals.add((short) 3, KMIntegerTag.instance().asDate()); - inst.vals.add((short) 4, KMIntegerArrayTag.instance().asUlongArray()); - inst.vals.add((short) 5, KMEnumTag.instance()); - inst.vals.add((short) 6, KMEnumArrayTag.instance()); - inst.vals.add((short) 7, KMByteTag.instance()); - inst.vals.add((short) 8, KMBoolTag.instance()); - return inst; + public static short findTag(short tagType, short tagKey, short keyParam) { + KMKeyParameters instParam = KMKeyParameters.cast(keyParam); + return instParam.findTag(tagType, tagKey); } - public static KMKeyParameters instance(KMArray vals) { - KMKeyParameters inst = repository.newKeyParameters(); - inst.vals = vals; - return inst; + public short findTag(short tagType, short tagKey){ + KMArray vals = KMArray.cast(getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short ret = KMType.INVALID_VALUE; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + if ((tagKey == key) && (tagType == type)) { + ret = obj; + break; + } + index++; + } + return ret; } - public static void create(KMKeyParameters[] keyParametersRefTable) { + // KDF, ECIES_SINGLE_HASH_MODE missing from types.hal + public static short makeHwEnforced( + short keyParamsPtr, byte origin, short osVersionObjPtr, short osPatchObjPtr, byte[] scratchPad) { + final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_TAG, KMType.ORIGIN, + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE + }; byte index = 0; - while (index < keyParametersRefTable.length) { - keyParametersRefTable[index] = new KMKeyParameters(); + short tagInd = 0; + short arrInd = 0; + short tagPtr = 0; + short tagKey = 0; + short tagType = 0; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) hwEnforcedTagArr.length) { + if ((hwEnforcedTagArr[tagInd] == tagType) + && (hwEnforcedTagArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } index++; } + short originTag = KMEnumTag.instance(KMType.ORIGIN, origin); + Util.setShort(scratchPad, arrInd, originTag); + arrInd += 2; + short osVersionTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION, osVersionObjPtr); + Util.setShort(scratchPad, arrInd, osVersionTag); + arrInd += 2; + short osPatchTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, osPatchObjPtr); + Util.setShort(scratchPad, arrInd, osPatchTag); + arrInd += 2; + return createKeyParameters(scratchPad, (short) (arrInd / 2)); } - public KMArray getVals() { - return vals; + // ALL_USERS, EXPORTABLE missing from types.hal + public static short makeSwEnforced(short keyParamsPtr, byte[] scratchPad) { + final short[] swEnforcedTagsArr = { + KMType.DATE_TAG, KMType.ACTIVE_DATETIME, + KMType.DATE_TAG, KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, + KMType.UINT_TAG, KMType.USERID, + KMType.DATE_TAG, KMType.CREATION_DATETIME + }; + byte index = 0; + short tagInd = 0; + short arrInd = 0; + short tagPtr = 0; + short tagKey = 0; + short tagType = 0; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)){ + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) swEnforcedTagsArr.length) { + if ((swEnforcedTagsArr[tagInd] == tagType) + && (swEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + } + short appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + } + return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); + } + + public static short makeHidden(short appIdBlob, short appDataBlob, short rootOfTrustBlob, byte[] scratchPad){ + // Order in which the hidden array is created should not change. + short index = 0; + KMByteBlob.cast(rootOfTrustBlob); + Util.setShort(scratchPad, index, rootOfTrustBlob); + index += 2; + if (appIdBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appIdBlob); + Util.setShort(scratchPad, index, appIdBlob); + index += 2; + } + if (appDataBlob != KMTag.INVALID_VALUE) { + Util.setShort(scratchPad, index, appDataBlob); + index += 2; + } + return createKeyParameters(scratchPad, (short)(index/2)); + + } + public static boolean isValidTag(short tagType, short tagKey) { + short[] invalidTagsArr = { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY + }; + short index = 0; + if (tagKey == KMType.INVALID_TAG) { + return false; + } + while (index < invalidTagsArr.length) { + if ((tagType == invalidTagsArr[index]) && (tagKey == invalidTagsArr[(short) (index + 1)])) { + return false; + } + index += 2; + } + return true; + } + + public static short createKeyParameters(byte[] ptrArr, short len) { + short arrPtr = KMArray.instance(len); + short index = 0; + short ptr = 0; + while (index < len) { + KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + index++; + ptr += 2; + } + return KMKeyParameters.instance(arrPtr); + } +/* + public void validateKeyCreation(){ + short alg = KMEnumTag.getValue(KMType.ALGORITHM,instPtr); + if(alg == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + short keySize = KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, instPtr); + short padding = KMEnumArrayTag. + validateKeySize(keySize, alg); + + if(padding != KMType.INVALID_VALUE){ + validatePadding(padding, alg); + } + if(digest != KMType.INVALID_VALUE){ + validateDigest(digest, alg); + } + if(purpose != KMType.INVALID_VALUE){ + validatePurpose(purpose, alg); + } + switch(alg){ + case KMType.RSA: + validateRsa(keySize, padding, digest, purpose, arrPtr); + break; + case KMType.AES: + validateAes(keySize, padding, digest, purpose); + break; + case KMType.DES: + validateDes(keySize, padding, digest, purpose); + break; + case KMType.EC: + validateEc(keySize, padding, digest, purpose); + break; + case KMType.HMAC: + validateHmac(keySize, padding, digest, purpose); + break; + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + break; + } + final byte[] alg = {, KMType.AES, KMType.DES, KMType.EC, KMType.HMAC}; + final Object[] keySizes = { + new short[]{2048}, + new short[]{128, 256}, + new short[]{192}, + new short[]{256}, + new short[]{128, 256, 512} + }; + final Object[] digests = { + new byte[]{KMType.DIGEST_NONE,KMType.SHA2_256}, + new byte[]{KMType.DIGEST_NONE}, + new byte[]{KMType.DIGEST_NONE}, + new byte[]{KMType.DIGEST_NONE,KMType.SHA2_256}, + new byte[]{KMType.SHA2_256} + }; + final Object[] paddings = { + new byte[]{KMType.PADDING_NONE, KMType.RSA_OAEP, KMType.RSA_PKCS1_1_5_ENCRYPT, KMType.RSA_PKCS1_1_5_SIGN, KMType.RSA_PSS}, + new byte[]{KMType.PADDING_NONE, KMType.PKCS7}, + new byte[]{KMType.PADDING_NONE, KMType.PKCS7}, + new byte[]{PADDING_NONE}, + new byte[]{PADDING_NONE} + }; + final Object[] purposes ={ + new byte[]{KMType.ENCRYPT, KMType.DECRYPT, KMType.SIGN, KMType.VERIFY}, + new byte[]{KMType.ENCRYPT, KMType.DECRYPT}, + new byte[]{KMType.ENCRYPT, KMType.DECRYPT}, + new byte[]{KMType.SIGN, KMType.VERIFY}, + new byte[]{KMType.SIGN, KMType.VERIFY} + }; + + } + short keySize = findTag(KMType.UINT_TAG, KMType.KEYSIZE, this); + short padding = findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, this); + short digest = findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, ); + short purpose = findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, keyParams); + + private void validateKeySize(short keySize, short alg){ + if() + } + private static void validateRsa(short keySize, short padding, short digest, short purpose, short arr) { + short ptr = + if(KMIntegerTag.cast(keySize).) + + } + + public boolean validateDigest(short digest){ + short index = 0; + while(index < KMEnumArrayTag.cast(digest).length()){ + if(KMEnumArrayTag.cast(digest).get(index) != KMType.DIGEST_NONE && + KMEnumArrayTag.cast(digest).get(index) != KMType.SHA2_256){ + return false; + } + index++; + } + return true; + } +*/ +/* + private static void print (String lab, byte[] b, short s, short l){ + byte[] i = new byte[l]; + Util.arrayCopyNonAtomic(b,s,i,(short)0,l); + print(lab,i); + } + private static void print(String label, byte[] buf){ + System.out.println(label+": "); + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < buf.length; i++){ + sb.append(String.format(" 0x%02X", buf[i])) ; + if(((i-1)%38 == 0) && ((i-1) >0)){ + sb.append(";\n"); + } + } + System.out.println(sb.toString()); } +*/ } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 83512f92..c65b8fb9 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -20,7 +20,19 @@ import javacard.framework.Applet; import javacard.framework.AppletEvent; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.Signature; import javacardx.apdu.ExtendedLength; /** @@ -32,29 +44,150 @@ // - remove this in future. public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength { // Constants. - public static final short MAX_LENGTH = (short) 0x04ff; // TODO: make this value configurable. + public static final byte AES_BLOCK_SIZE = 16; + public static final byte DES_BLOCK_SIZE = 8; + public static final short MAX_LENGTH = (short) 0x2000; private static final byte CLA_ISO7816_NO_SM_NO_CHAN = (byte) 0x80; - private static final byte KM_HAL_VERSION = (byte) 0x41; + private static final short KM_HAL_VERSION = (short) 0x4000; + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + private static final short MAX_IO_LENGTH = 0x400; + // "Keymaster HMAC Verification" - used for HMAC key verification. + public static final byte[] sharingCheck = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x48, 0x4D, 0x41, 0x43, 0x20, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E + }; + // "KeymasterSharedMac" + public static final byte[] ckdfLable = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, + 0x61, 0x63 + }; + // "Auth Verification" + public static final byte[] authVerification = {0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E}; + // "confirmation token" + public static final byte[] confirmationToken = {0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, + 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B, 0x65, 0x6E}; // Possible states of the applet. - public static final byte ILLEGAL_STATE = 0x00; - public static final byte INSTALL_STATE = 0x01; - public static final byte FIRST_SELECT_STATE = 0x02; - public static final byte ACTIVE_STATE = 0x03; - public static final byte INACTIVE_STATE = 0x04; - public static final byte UNINSTALLED_STATE = 0x05; + private static final byte ILLEGAL_STATE = 0x00; + private static final byte INSTALL_STATE = 0x01; + private static final byte FIRST_SELECT_STATE = 0x02; + private static final byte ACTIVE_STATE = 0x03; + private static final byte INACTIVE_STATE = 0x04; + private static final byte UNINSTALLED_STATE = 0x05; + // Commands + private static final byte INS_GENERATE_KEY_CMD = 0x10; + private static final byte INS_IMPORT_KEY_CMD = 0x11; + private static final byte INS_IMPORT_WRAPPED_KEY_CMD = 0x12; + private static final byte INS_EXPORT_KEY_CMD = 0x13; + private static final byte INS_ATTEST_KEY_CMD = 0x14; + private static final byte INS_UPGRADE_KEY_CMD = 0x15; + private static final byte INS_DELETE_KEY_CMD = 0x16; + private static final byte INS_DELETE_ALL_KEYS_CMD = 0x17; + private static final byte INS_ADD_RNG_ENTROPY_CMD = 0x18; + private static final byte INS_COMPUTE_SHARED_HMAC_CMD = 0x19; + private static final byte INS_DESTROY_ATT_IDS_CMD = 0x1A; + private static final byte INS_VERIFY_AUTHORIZATION_CMD = 0x1B; + private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = 0x1C; + private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = 0x1D; + private static final byte INS_GET_HW_INFO_CMD = 0x1E; + private static final byte INS_BEGIN_OPERATION_CMD = 0x1F; + private static final byte INS_UPDATE_OPERATION_CMD = 0x20; + private static final byte INS_FINISH_OPERATION_CMD = 0x21; + private static final byte INS_ABORT_OPERATION_CMD = 0x22; + private static final byte INS_PROVISION_CMD = 0x23; + private static final byte INS_SET_BOOT_PARAMS_CMD = 0x24; + // Data Dictionary items + public static final byte DATA_ARRAY_SIZE = 30; + public static final byte TMP_VARIABLE_ARRAY_SIZE = 20; + public static final byte UPDATE_PARAM_ARRAY_SIZE = 40; + public static final byte KEY_PARAMETERS = 0; + public static final byte KEY_CHARACTERISTICS = 1; + public static final byte HIDDEN_PARAMETERS = 2; + public static final byte HW_PARAMETERS = 3; + public static final byte SW_PARAMETERS = 4; + public static final byte AUTH_DATA = 5; + public static final byte AUTH_TAG = 6; + public static final byte NONCE = 7; + public static final byte KEY_BLOB = 8; + public static final byte AUTH_DATA_LENGTH = 9; + public static final byte SECRET = 10; + public static final byte ROT = 11; + public static final byte DERIVED_KEY = 12; + public static final byte RSA_PUB_EXPONENT = 13; + public static final byte APP_ID = 14; + public static final byte APP_DATA = 15; + public static final byte PUB_KEY = 16; + public static final byte IMPORTED_KEY_BLOB = 17; + public static final byte ORIGIN = 18; + public static final byte ENC_TRANSPORT_KEY = 19; + public static final byte MASKING_KEY = 20; + public static final byte HMAC_SHARING_PARAMS = 21; + public static final byte OP_HANDLE = 22; + public static final byte IV = 23; + public static final byte INPUT_DATA = 24; + public static final byte OUTPUT_DATA = 25; + public static final byte HW_TOKEN = 26; + public static final byte VERIFICATION_TOKEN = 27; + private static final byte SIGNATURE = 28; - // State of the applet. - private byte keymasterState = ILLEGAL_STATE; - private KMRepository repository; + // AddRngEntropy + private static final short MAX_SEED_SIZE = 2048; + // Keyblob constants + public static final byte KEY_BLOB_SECRET = 0; + public static final byte KEY_BLOB_NONCE = 1; + public static final byte KEY_BLOB_AUTH_TAG = 2; + public static final byte KEY_BLOB_KEYCHAR = 3; + public static final byte KEY_BLOB_PUB_KEY = 4; + // AES GCM constants + private static final byte AES_GCM_AUTH_TAG_LENGTH = 12; + private static final byte AES_GCM_NONCE_LENGTH = 12; + // ComputeHMAC constants + private static final short HMAC_SEED_SIZE = 32; + private static final short HMAC_NONCE_SIZE = 32; + // Keymaster Applet attributes + private static byte keymasterState = ILLEGAL_STATE; + private static KMEncoder encoder; + private static KMDecoder decoder; + private static KMRepository repository; + private static KMCryptoProvider cryptoProvider; + private static byte[] buffer; + private static short bufferLength; + private static short bufferStartOffset; + private static boolean provisionDone; + private static boolean setBootParamsDone; + private static short[] tmpVariables; + private static short[] data; - /** - * Registers this applet. - * - * @param repo reference to the repository which manages all the NVM objects. - */ - protected KMKeymasterApplet(KMRepository repo) { - repository = repo; + /** Registers this applet. */ + protected KMKeymasterApplet() { + // TODO change this to make this compile time variation. + cryptoProvider = KMCryptoProviderImpl.instance(); + provisionDone = false; + setBootParamsDone = false; + byte[] buf = + JCSystem.makeTransientByteArray( + repository.HMAC_SEED_NONCE_SIZE, JCSystem.CLEAR_ON_DESELECT); + keymasterState = KMKeymasterApplet.INSTALL_STATE; + data = JCSystem.makeTransientShortArray((short) DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_RESET); + repository = new KMRepository(); + tmpVariables = + JCSystem.makeTransientShortArray((short) TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_RESET); + Util.arrayCopyNonAtomic( + cryptoProvider.getTrueRandomNumber(repository.HMAC_SEED_NONCE_SIZE), + (short) 0, + buf, + (short) 0, + repository.HMAC_SEED_NONCE_SIZE); + repository.initMasterKey(buf, repository.HMAC_SEED_NONCE_SIZE); + cryptoProvider.newRandomNumber(buf, (short) 0, repository.HMAC_SEED_NONCE_SIZE); + // TODO remove this when key agreement protocol is implemented. + repository.initHmacKey(buf, repository.HMAC_SEED_NONCE_SIZE); + cryptoProvider.newRandomNumber(buf, (short) 0, repository.HMAC_SEED_NONCE_SIZE); + repository.initHmacSeed(buf, repository.HMAC_SEED_NONCE_SIZE); + KMType.initialize(); + encoder = new KMEncoder(); + decoder = new KMDecoder(); register(); } @@ -66,11 +199,7 @@ protected KMKeymasterApplet(KMRepository repo) { * @param bLength the length in bytes of the parameter data in bArray */ public static void install(byte[] bArray, short bOffset, byte bLength) { - KMRepository repo = new KMRepository(); - // TODO: Read the configuration from the package and pass the data in initialize method. - repo.initialize(); - KMKeymasterApplet keymaster = new KMKeymasterApplet(repo); - keymaster.setKeymasterState(KMKeymasterApplet.INSTALL_STATE); + new KMKeymasterApplet(); } /** @@ -81,10 +210,10 @@ public static void install(byte[] bArray, short bOffset, byte bLength) { @Override public boolean select() { repository.onSelect(); - if (getKeymasterState() == KMKeymasterApplet.INSTALL_STATE) { - setKeymasterState(KMKeymasterApplet.FIRST_SELECT_STATE); - } else if (getKeymasterState() == KMKeymasterApplet.INACTIVE_STATE) { - setKeymasterState(KMKeymasterApplet.ACTIVE_STATE); + if (keymasterState == KMKeymasterApplet.INSTALL_STATE) { + keymasterState = KMKeymasterApplet.FIRST_SELECT_STATE; + } else if (keymasterState == KMKeymasterApplet.INACTIVE_STATE) { + keymasterState = KMKeymasterApplet.ACTIVE_STATE; } else { return false; } @@ -95,8 +224,8 @@ public boolean select() { @Override public void deselect() { repository.onDeselect(); - if (getKeymasterState() == KMKeymasterApplet.ACTIVE_STATE) { - setKeymasterState(KMKeymasterApplet.INACTIVE_STATE); + if (keymasterState == KMKeymasterApplet.ACTIVE_STATE) { + keymasterState = KMKeymasterApplet.INACTIVE_STATE; } } @@ -104,24 +233,23 @@ public void deselect() { @Override public void uninstall() { repository.onUninstall(); - if (getKeymasterState() != KMKeymasterApplet.UNINSTALLED_STATE) { - setKeymasterState(KMKeymasterApplet.UNINSTALLED_STATE); + if (keymasterState != KMKeymasterApplet.UNINSTALLED_STATE) { + keymasterState = KMKeymasterApplet.UNINSTALLED_STATE; } } /** * Processes an incoming APDU and handles it using command objects. * - * @see APDU * @param apdu the incoming APDU */ @Override public void process(APDU apdu) { repository.onProcess(); // Verify whether applet is in correct state. - if ((getKeymasterState() != KMKeymasterApplet.ACTIVE_STATE) - && (getKeymasterState() != KMKeymasterApplet.FIRST_SELECT_STATE)) { - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + if ((keymasterState != KMKeymasterApplet.ACTIVE_STATE) + && (keymasterState != KMKeymasterApplet.FIRST_SELECT_STATE)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } // If this is select applet apdu which is selecting this applet then return if (apdu.isISOInterindustryCLA()) { @@ -129,105 +257,2902 @@ public void process(APDU apdu) { return; } } - // Read the apdu header and buffer. - byte[] buffer = apdu.getBuffer(); - byte apduClass = buffer[ISO7816.OFFSET_CLA]; - byte apduIns = buffer[ISO7816.OFFSET_INS]; - byte halVersion = buffer[ISO7816.OFFSET_P1]; - byte apduP2 = buffer[ISO7816.OFFSET_P2]; - + byte[] apduBuffer = apdu.getBuffer(); + byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; + byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + buffer = repository.getHeap(); + bufferStartOffset = repository.alloc(MAX_IO_LENGTH); // Validate APDU Header. if ((apduClass != CLA_ISO7816_NO_SM_NO_CHAN)) { - throw new KMException(ISO7816.SW_CLA_NOT_SUPPORTED); - } else if ((halVersion != KMKeymasterApplet.KM_HAL_VERSION) && (apduP2 != (byte) 0x00)) { - throw new KMException(ISO7816.SW_INCORRECT_P1P2); + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + } else if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { + ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } - - // Process the APDU. + // Validate whether INS can be supported + if (!(apduIns >= INS_GENERATE_KEY_CMD && apduIns <= INS_SET_BOOT_PARAMS_CMD)) { + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + // Validate if INS is provision command if applet is in FIRST_SELECT_STATE. + if (keymasterState == KMKeymasterApplet.FIRST_SELECT_STATE) { + if ((apduIns != INS_PROVISION_CMD) && (apduIns != INS_SET_BOOT_PARAMS_CMD)) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + if (apduIns == INS_PROVISION_CMD && provisionDone) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + if (apduIns == INS_SET_BOOT_PARAMS_CMD && setBootParamsDone) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + } + // Process the apdu try { - // Get the command object for specific INS from the repository. - KMCommand command = repository.getCommand(apduIns); - // Get the empty context object from the repository. - KMContext context = repository.getContext(); - // Initialize context - context.setKeymasterState(getKeymasterState()); - context.setBuffer(repository.getBuffer()); - if(command.hasArguments()){ - receiveIncoming(context, apdu); - } - // Execute the command. If the execution fails then an exception is thrown. - command.execute(context); - - // context has data that needs to be sent - if(context.getBufferLength() >0 ){ - sendOutgoing(context, apdu); - } - - // Update the Keymaster state according to the context. - setKeymasterState(context.getKeymasterState()); + // Handle the command + switch (apduIns) { + case INS_GENERATE_KEY_CMD: + processGenerateKey(apdu); + break; + case INS_IMPORT_KEY_CMD: + processImportKeyCmd(apdu); + break; + case INS_IMPORT_WRAPPED_KEY_CMD: + processImportWrappedKeyCmd(apdu); + break; + case INS_EXPORT_KEY_CMD: + processExportKeyCmd(apdu); + break; + case INS_ATTEST_KEY_CMD: + processAttestKeyCmd(apdu); + break; + case INS_UPGRADE_KEY_CMD: + processUpgradeKeyCmd(apdu); + break; + case INS_DELETE_KEY_CMD: + processDeleteKeyCmd(apdu); + break; + case INS_DELETE_ALL_KEYS_CMD: + processDeleteAllKeysCmd(apdu); + break; + case INS_ADD_RNG_ENTROPY_CMD: + processAddRngEntropyCmd(apdu); + break; + case INS_COMPUTE_SHARED_HMAC_CMD: + processComputeSharedHmacCmd(apdu); + break; + case INS_DESTROY_ATT_IDS_CMD: + processDestroyAttIdsCmd(apdu); + break; + case INS_VERIFY_AUTHORIZATION_CMD: + processVerifyAuthorizationCmd(apdu); + break; + case INS_GET_HMAC_SHARING_PARAM_CMD: + processGetHmacSharingParamCmd(apdu); + break; + case INS_GET_KEY_CHARACTERISTICS_CMD: + processGetKeyCharacteristicsCmd(apdu); + break; + case INS_GET_HW_INFO_CMD: + processGetHwInfoCmd(apdu); + break; + case INS_BEGIN_OPERATION_CMD: + processBeginOperationCmd(apdu); + break; + case INS_UPDATE_OPERATION_CMD: + processUpdateOperationCmd(apdu); + break; + case INS_FINISH_OPERATION_CMD: + processFinishOperationCmd(apdu); + break; + case INS_ABORT_OPERATION_CMD: + processAbortOperationCmd(apdu); + break; + case INS_PROVISION_CMD: + processProvisionCmd(apdu); + break; + case INS_SET_BOOT_PARAMS_CMD: + processSetBootParamsCmd(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } } catch (KMException exception) { - // TODO: error handling for command related error. - // TODO: This should result in ISOException or exception with keymaster specific error codes + if(data[OP_HANDLE] != KMType.INVALID_VALUE){ + KMOperationState op = repository.findOperation(KMInteger.cast(data[OP_HANDLE]).getShort()); + if(op != null){ + repository.releaseOperation(op); + } + } + sendError(apdu, exception.reason); + exception.clear(); + } finally { + resetData(); + repository.clean(); } } - - /** - * Sends a response, may be extended response, as requested by the command. - * - * @param context of current command. - */ - public void sendOutgoing(KMContext context, APDU apdu) { - // Initialize source - short srcLength = context.getBufferLength(); - if (srcLength > MAX_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + private void resetData(){ + short index = 0; + while (index < data.length){ + data[index] = KMType.INVALID_VALUE; + index++; + } + } + /** Sends a response, may be extended response, as requested by the command. */ + public static void sendOutgoing(APDU apdu) { + if (bufferLength > MAX_IO_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } // Send data - byte[] srcBuffer = context.getBuffer(); apdu.setOutgoing(); - apdu.setOutgoingLength(srcLength); - apdu.sendBytesLong(srcBuffer, (short) 0, srcLength); + // short currentBlockSize = apdu.getOutBlockSize(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); } - /** - * Receives data, which can be extended data, as requested by the command instance. - * - * @param context of current command. - */ - public void receiveIncoming(KMContext context, APDU apdu) { - // Initialize source + /** Receives data, which can be extended data, as requested by the command instance. */ + public static void receiveIncoming(APDU apdu) { byte[] srcBuffer = apdu.getBuffer(); - // Initialize destination - byte[] destBuffer = context.getBuffer(); - short destOffset = (short) 0; - - // Receive data short recvLen = apdu.setIncomingAndReceive(); short srcOffset = apdu.getOffsetCdata(); - short srcLength = apdu.getIncomingLength(); - if (srcLength > MAX_LENGTH) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); + bufferLength = apdu.getIncomingLength(); + short index = bufferStartOffset; + // Receive data + if (bufferLength > MAX_IO_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } - while (recvLen > 0) { - Util.arrayCopyNonAtomic(srcBuffer, srcOffset, destBuffer, destOffset, recvLen); - destOffset += recvLen; + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; recvLen = apdu.receiveBytes(srcOffset); } - // Update the Context - context.setBufferLength(srcLength); } - /** - * Getter for keymaster state. - * - * @return keymasterState - current state of the applet. - */ - private byte getKeymasterState() { - return keymasterState; + private void processGetHwInfoCmd(APDU apdu) { + // No arguments expected + final byte[] JavacardKeymasterDevice = { + 0x4A, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + }; + final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + + // Make the response + short respPtr = KMArray.instance((short) 3); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + resp.add( + (short) 1, + KMByteBlob.instance( + JavacardKeymasterDevice, (short) 0, (short) JavacardKeymasterDevice.length)); + resp.add((short) 2, KMByteBlob.instance(Google, (short) 0, (short) Google.length)); + // Encode the response - actual bufferLength is 86 + bufferLength = encoder.encode(respPtr, buffer, bufferStartOffset); + // send buffer to master + sendOutgoing(apdu); + } + + private void processAddRngEntropyCmd(APDU apdu) { + // Receive the incoming request fully from the master. + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) apdu.getBuffer().length, (byte) 0); + // Argument 1 + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMByteBlob.exp()); + // Decode the argument + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + // Process + KMByteBlob blob = KMByteBlob.cast(KMArray.cast(args).get((short) 0)); + // Maximum 2KiB of seed is allowed. + if (blob.length() > MAX_SEED_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + cryptoProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length()); + } + + private void processProvisionCmd(APDU apdu) { + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormat = KMEnum.instance(KMType.KEY_FORMAT); + short keyBlob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormat); + KMArray.cast(argsProto).add((short) 2, keyBlob); + // Decode the argument + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + // key params should have os patch, os version and verified root of trust + + // TODO execute the function + // Change the state to ACTIVE + if (keymasterState == KMKeymasterApplet.FIRST_SELECT_STATE) { + provisionDone = true; + if (setBootParamsDone) { + keymasterState = KMKeymasterApplet.ACTIVE_STATE; + } + } + } + + private void processGetKeyCharacteristicsCmd(APDU apdu) { + // Receive the incoming request fully from the master. + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Arguments + tmpVariables[0] = KMArray.instance((short) 3); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + KMArray.cast(tmpVariables[0]).add((short) 1, KMByteBlob.exp()); + KMArray.cast(tmpVariables[0]).add((short) 2, KMByteBlob.exp()); + // Decode the arguments + tmpVariables[0] = decoder.decode(tmpVariables[0], buffer, bufferStartOffset, bufferLength); + data[KEY_BLOB] = KMArray.cast(tmpVariables[0]).get((short) 0); + data[APP_ID] = KMArray.cast(tmpVariables[0]).get((short) 1); + data[APP_DATA] = KMArray.cast(tmpVariables[0]).get((short) 2); + if (!KMByteBlob.cast(data[APP_ID]).isValid()) { + data[APP_ID] = KMType.INVALID_VALUE; + } + if (!KMByteBlob.cast(data[APP_DATA]).isValid()) { + data[APP_DATA] = KMType.INVALID_VALUE; + } + // Parse Key Blob + parseEncryptedKeyBlob(scratchPad); + // Check Version and Patch Level + checkVersionAndPatchLevel(scratchPad); + // make response. + tmpVariables[0] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, data[KEY_CHARACTERISTICS]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void processGetHmacSharingParamCmd(APDU apdu) { + // No Arguments + byte[] scratchPad = apdu.getBuffer(); + // Create blob containing seed + tmpVariables[0] = + KMByteBlob.instance(repository.getHmacSeed(), (short) 0, repository.HMAC_SEED_NONCE_SIZE); + // Create blob containing nonce + cryptoProvider.newRandomNumber(scratchPad, (short) 0, repository.HMAC_SEED_NONCE_SIZE); + tmpVariables[1] = KMByteBlob.instance(scratchPad, (short) 0, repository.HMAC_SEED_NONCE_SIZE); + // Create HMAC Sharing Parameters + tmpVariables[2] = KMHmacSharingParameters.instance(); + KMHmacSharingParameters.cast(tmpVariables[2]).setNonce(tmpVariables[1]); + KMHmacSharingParameters.cast(tmpVariables[2]).setSeed(tmpVariables[0]); + // prepare the response + tmpVariables[3] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[3]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[3]).add((short) 1, tmpVariables[2]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void processDeleteAllKeysCmd(APDU apdu) { + // No arguments + repository.removeAllAuthTags(); + // Send ok + sendError(apdu, KMError.OK); + } + + private void processDeleteKeyCmd(APDU apdu) { + // Receive the incoming request fully from the master. + receiveIncoming(apdu); + // Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) apdu.getBuffer().length, (byte) 0); + // Arguments + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMByteBlob.exp()); + // Decode the argument + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + // Process + data[KEY_BLOB] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMByteBlob.cast(data[KEY_BLOB]).getStartOff(); + tmpVariables[1] = KMArray.instance((short) 5); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_SECRET, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_NONCE, KMByteBlob.exp()); + tmpVariables[2] = KMKeyCharacteristics.exp(); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_KEYCHAR, tmpVariables[2]); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, KMByteBlob.exp()); + data[KEY_BLOB] = + decoder.decodeArray( + tmpVariables[1], + KMByteBlob.cast(data[KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[KEY_BLOB]).length()); + tmpVariables[0] = KMArray.cast(data[KEY_BLOB]).length(); + if (tmpVariables[0] < 4) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Validate Auth Tag + data[AUTH_TAG] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_AUTH_TAG); + if (!repository.validateAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // delete the auth tag + repository.removeAuthTag(data[AUTH_TAG]); + // Send ok + sendError(apdu, KMError.OK); + } + + private void processComputeSharedHmacCmd(APDU apdu) { + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 1); + tmpVariables[2] = KMKeyParameters.exp(); + tmpVariables[3] = KMHmacSharingParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, KMArray.exp(tmpVariables[3])); // Vector of hmac params + KMArray.cast(tmpVariables[1]).add((short) 1, tmpVariables[2]); // Key Params + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 1); + data[HMAC_SHARING_PARAMS] = KMArray.cast(tmpVariables[2]).get((short) 0); + // Concatenate HMAC Params + tmpVariables[0] = 0; + tmpVariables[1] = KMArray.cast(data[HMAC_SHARING_PARAMS]).length();//total number of params + tmpVariables[5] = 0; // index in scratchPad + while (tmpVariables[0] < tmpVariables[1]) { + // read HmacSharingParam + tmpVariables[2] = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(tmpVariables[0]); + // get seed + tmpVariables[3] = KMHmacSharingParameters.cast(tmpVariables[2]).getSeed(); + tmpVariables[4] = KMByteBlob.cast(tmpVariables[3]).length(); + // if seed is present + if (tmpVariables[4] == HMAC_SEED_SIZE /*32*/) { + // then copy that to scratchPad + Util.arrayCopyNonAtomic( + KMByteBlob.cast(tmpVariables[3]).getBuffer(), + KMByteBlob.cast(tmpVariables[3]).getStartOff(), + scratchPad, + tmpVariables[5],// index in scratch pad + tmpVariables[4]); + tmpVariables[5] += tmpVariables[4]; // increment by seed length + } + // if nonce is present get nonce + tmpVariables[3] = KMHmacSharingParameters.cast(tmpVariables[2]).getNonce(); + tmpVariables[4] = KMByteBlob.cast(tmpVariables[3]).length(); + // if nonce is not present + if (tmpVariables[4] != HMAC_NONCE_SIZE /*32*/) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // copy nonce to scratchPad + Util.arrayCopyNonAtomic( + KMByteBlob.cast(tmpVariables[3]).getBuffer(), + KMByteBlob.cast(tmpVariables[3]).getStartOff(), + scratchPad, + tmpVariables[5], + tmpVariables[4]); + tmpVariables[5] += tmpVariables[4]; // increment by nonce length + tmpVariables[0]++; // go to next hmac param in the vector + } + // ckdf to derive hmac key - scratch pad has the context + HMACKey key = + cryptoProvider.cmacKdf( + repository.getHmacKey(), ckdfLable , scratchPad, (short) 0, tmpVariables[5]); + tmpVariables[5] = key.getKey(scratchPad, (short) 0); + repository.initComputedHmac(scratchPad, (short) 0, tmpVariables[5]); + // Generate sharingKey verification + tmpVariables[5] = + cryptoProvider.hmacSign( + key, sharingCheck, (short) 0, (short) sharingCheck.length, scratchPad, (short) 0); + tmpVariables[1] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[5]); + // prepare the response + tmpVariables[0] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, tmpVariables[1]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void processUpgradeKeyCmd(APDU apdu) { + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 2); + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, KMByteBlob.exp()); // Key Blob + KMArray.cast(tmpVariables[1]).add((short) 1, tmpVariables[2]); // Key Params + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[KEY_BLOB] = KMArray.cast(tmpVariables[2]).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 1); + tmpVariables[0] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMTag.INVALID_VALUE) { + data[APP_ID] = KMByteTag.cast(tmpVariables[0]).getValue(); + } + tmpVariables[0] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMTag.INVALID_VALUE) { + data[APP_DATA] = KMByteTag.cast(tmpVariables[0]).getValue(); + } + // parse existing key blob + parseEncryptedKeyBlob(scratchPad); + // validate characteristics to be upgraded. + // TODO currently only os version and os patch level are upgraded. + tmpVariables[0] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.OS_VERSION, data[HW_PARAMETERS]); + tmpVariables[0] = KMIntegerTag.cast(tmpVariables[0]).getValue(); + tmpVariables[1] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, data[HW_PARAMETERS]); + tmpVariables[1] = KMIntegerTag.cast(tmpVariables[1]).getValue(); + tmpVariables[2] = KMInteger.uint_32(repository.osVersion,(short)0); + tmpVariables[3] = KMInteger.uint_32(repository.osPatch,(short)0); + tmpVariables[4] = KMInteger.uint_8((byte)0); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + // os version in key characteristics must be less the os version stored in javacard or the + // stored version must be zero. Then only upgrade is allowed else it is invalid argument. + if(KMInteger.compare(tmpVariables[0], tmpVariables[2]) != -1 && + KMInteger.compare(tmpVariables[2], tmpVariables[4]) != 0){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + if(tmpVariables[1] != KMType.INVALID_VALUE){ + // The key characteristics should have has os patch level < os patch level stored in javacard + // then only upgrade is allowed. + if(KMInteger.compare(tmpVariables[1], tmpVariables[3]) != -1){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } +/* KMIntegerTag.getValue( + scratchPad, (short) 0, ); + if ((tmpVariables[0] != KMType.INVALID_VALUE) + && (Util.arrayCompare( + repository.osVersion, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + != 0)) { + if (Util.arrayCompare(repository.osVersion, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + == -1) { + // If the key characteristics has os version > current os version + Util.arrayFillNonAtomic(scratchPad, (short) 0, tmpVariables[0], (byte) 0); + // If the os version is not zero + if (Util.arrayCompare( + repository.osVersion, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + != 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + } + tmpVariables[0] = + KMIntegerTag.getValue( + scratchPad, (short) 0, KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, data[HW_PARAMETERS]); + if ((tmpVariables[0] != KMType.INVALID_VALUE) + && (Util.arrayCompare(repository.osPatch, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + != 0)) { + if (Util.arrayCompare(repository.osPatch, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + < 0) { + // If the key characteristics has os patch level > current os patch + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + }*/ + // remove Auth Tag + repository.removeAuthTag(data[AUTH_TAG]); + // copy origin + data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]); + // create new key blob with current os version etc. + createEncryptedKeyBlob(scratchPad); + // persist new auth tag for rollback resistance. + repository.persistAuthTag(data[AUTH_TAG]); + // prepare the response + tmpVariables[0] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, data[KEY_BLOB]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void processExportKeyCmd(APDU apdu) { + sendError(apdu, KMError.UNIMPLEMENTED); + } + + private void processImportWrappedKeyCmd(APDU apdu) { + // Currently only RAW formatted import key blob are supported + if (repository.keyBlobCount > repository.MAX_BLOB_STORAGE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 11); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, tmpVariables[2]); // Key Params + KMArray.cast(tmpVariables[1]).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(tmpVariables[1]).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.cast(tmpVariables[1]).add((short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.cast(tmpVariables[1]).add((short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.cast(tmpVariables[1]).add((short) 5, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.cast(tmpVariables[1]).add((short) 6, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.cast(tmpVariables[1]).add((short) 7, KMByteBlob.exp()); // Masking Key + KMArray.cast(tmpVariables[1]).add((short) 8, tmpVariables[2]); // Un-wrapping Params + KMArray.cast(tmpVariables[1]).add((short) 9, KMInteger.exp()); // Password Sid + KMArray.cast(tmpVariables[1]).add((short) 10, KMInteger.exp()); // Biometric Sid + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + tmpVariables[3] = KMArray.cast(tmpVariables[2]).get((short) 0); + // get algorithm + tmpVariables[3] = KMEnumTag.getValue(KMType.ALGORITHM, tmpVariables[3]); + if (tmpVariables[3] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[3] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[3] == KMType.RSA + || tmpVariables[3] == KMType.EC) { // RSA and EC not implemented + KMException.throwIt(KMError.UNIMPLEMENTED); + } + // Key format must be RAW format - X509 and PKCS8 not implemented. + tmpVariables[3] = KMArray.cast(tmpVariables[2]).get((short) 1); + tmpVariables[3] = KMEnum.cast(tmpVariables[3]).getVal(); + if (tmpVariables[3] != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + data[AUTH_DATA] = KMArray.cast(tmpVariables[2]).get((short) 3); + data[AUTH_TAG] = KMArray.cast(tmpVariables[2]).get((short) 4); + data[NONCE] = KMArray.cast(tmpVariables[2]).get((short) 5); + data[ENC_TRANSPORT_KEY] = KMArray.cast(tmpVariables[2]).get((short) 6); + data[MASKING_KEY] = KMArray.cast(tmpVariables[2]).get((short) 8); + // Step 1 - parse wrapping key blob + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 9); // wrapping key parameters + // Check for app id and app data. + data[APP_ID] = KMType.INVALID_VALUE; + data[APP_DATA] = KMType.INVALID_VALUE; + tmpVariables[3] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + if (tmpVariables[3] != KMTag.INVALID_VALUE) { + data[APP_ID] = KMByteTag.cast(tmpVariables[3]).getValue(); + } + tmpVariables[3] = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + if (tmpVariables[3] != KMTag.INVALID_VALUE) { + data[APP_DATA] = KMByteTag.cast(tmpVariables[3]).getValue(); + } + // wrapping key blob + data[KEY_BLOB] = KMArray.cast(tmpVariables[2]).get((short) 7); + parseEncryptedKeyBlob(scratchPad); + + // Step 2 - Decrypt the encrypted transport key + // enforce authorization for WRAP_KEY operation using RSA algorithm according to javacard caps. + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + if (!(KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS]))) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!(KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS]))) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + KMCipher cipher = + cryptoProvider.createRsaDecipher( + KMCipher.PAD_PKCS1_OAEP_SHA256, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + // Decrypt the transport key + tmpVariables[3] = + cipher.doFinal( + KMByteBlob.cast(data[ENC_TRANSPORT_KEY]).getBuffer(), + KMByteBlob.cast(data[ENC_TRANSPORT_KEY]).getStartOff(), + KMByteBlob.cast(data[ENC_TRANSPORT_KEY]).length(), + scratchPad, + (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[3]); + cryptoProvider.delete(cipher); + + // Step 3 - XOR with masking key + tmpVariables[4] = KMByteBlob.cast(data[MASKING_KEY]).length(); + if (tmpVariables[3] != tmpVariables[4]) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + tmpVariables[3] = 0; // index in scratchPad + byte[] buf = KMByteBlob.cast(MASKING_KEY).getBuffer(); + tmpVariables[5] = KMByteBlob.cast(MASKING_KEY).getStartOff(); + while (tmpVariables[3] < tmpVariables[4]) { + scratchPad[tmpVariables[3]] = + (byte) (scratchPad[tmpVariables[3]] ^ buf[(short) (tmpVariables[3] + tmpVariables[5])]); + scratchPad[3]++; + } + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[3]); + + // Step 4 - AES-GCM decrypt + data[IMPORTED_KEY_BLOB] = KMArray.cast(tmpVariables[2]).get((short) 2); + data[AUTH_DATA] = KMArray.cast(tmpVariables[2]).get((short) 3); + data[AUTH_TAG] = KMArray.cast(tmpVariables[2]).get((short) 4); + data[NONCE] = KMArray.cast(tmpVariables[2]).get((short) 5); + data[ENC_TRANSPORT_KEY] = KMArray.cast(tmpVariables[2]).get((short) 6); + data[MASKING_KEY] = KMArray.cast(tmpVariables[2]).get((short) 8); + AESKey key = + cryptoProvider.createAESKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + boolean verification = + cryptoProvider.aesGCMDecrypt( + key, + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + KMByteBlob.cast(data[AUTH_DATA]).getBuffer(), + KMByteBlob.cast(data[AUTH_DATA]).getStartOff(), + KMByteBlob.cast(data[AUTH_DATA]).length(), + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + if (verification == false) { + KMException.throwIt(KMError.IMPORTED_KEY_VERIFICATION_FAILED); + } + cryptoProvider.delete(key); + + // Step 5 - Import Decrypted Key. + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 0); + importKey(apdu, scratchPad); + } + + private void processAttestKeyCmd(APDU apdu) {} + + private void processDestroyAttIdsCmd(APDU apdu) {} + + private void processVerifyAuthorizationCmd(APDU apdu) { + sendError(apdu, KMError.UNIMPLEMENTED); + } + + private void processAbortOperationCmd(APDU apdu) { + receiveIncoming(apdu); + tmpVariables[1] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[1]).add((short) 0, KMInteger.exp()); + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[OP_HANDLE] = KMArray.cast(tmpVariables[2]).get((short) 0); + tmpVariables[1] = KMInteger.cast(data[OP_HANDLE]).getShort(); + KMOperationState op = repository.findOperation(tmpVariables[1]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + repository.releaseOperation(op); + sendError(apdu, KMError.OK); + } + + private void processFinishOperationCmd(APDU apdu) { + // TODO AES GCM + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 6); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, KMInteger.exp()); + KMArray.cast(tmpVariables[1]).add((short) 1, tmpVariables[2]); + KMArray.cast(tmpVariables[1]).add((short) 2, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add((short) 3, KMByteBlob.exp()); + tmpVariables[3] = KMHardwareAuthToken.exp(); + KMArray.cast(tmpVariables[1]).add((short) 4, tmpVariables[3]); + tmpVariables[4] = KMVerificationToken.exp(); + KMArray.cast(tmpVariables[1]).add((short) 5, tmpVariables[4]); + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[OP_HANDLE] = KMArray.cast(tmpVariables[2]).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 1); + data[INPUT_DATA] = KMArray.cast(tmpVariables[2]).get((short) 2); + data[SIGNATURE] = KMArray.cast(tmpVariables[2]).get((short) 3); + data[HW_TOKEN] = KMArray.cast(tmpVariables[2]).get((short) 4); + data[VERIFICATION_TOKEN] = KMArray.cast(tmpVariables[2]).get((short) 5); + // Check Operation Handle + tmpVariables[1] = KMInteger.cast(data[OP_HANDLE]).getShort(); + KMOperationState op = repository.findOperation(tmpVariables[1]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + //Authorize the finish operation + authorizeUpdateFinishOperation(op, scratchPad); + //Finish trusted Confirmation operation + switch(op.getPurpose()){ + case KMType.SIGN: + finishTrustedConfirmationOperation(op); + case KMType.VERIFY: + finishSigningVerifyingOperation(op,scratchPad); + break; + case KMType.ENCRYPT: + finishEncryptOperation(op, scratchPad); + break; + case KMType.DECRYPT: + finishDecryptOperation(op,scratchPad); + break; + } + // Remove the operation handle + repository.releaseOperation(op); + // make response + tmpVariables[1] = KMArray.instance((short) 0); + tmpVariables[1] = KMKeyParameters.instance(tmpVariables[1]); + tmpVariables[2] = KMArray.instance((short) 3); + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + KMArray.cast(tmpVariables[2]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[2]).add((short) 1, tmpVariables[1]); + KMArray.cast(tmpVariables[2]).add((short) 2, data[OUTPUT_DATA]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[2], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch(op.getAlgorithm()){ + // Only supported for testing purpose + // TODO remove this later on + case KMType.RSA: + data[OUTPUT_DATA] = KMByteBlob.instance((short)256); + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + if(op.getPadding() == KMType.PADDING_NONE){ + // Length cannot be greater then key size - we restrict this to 255 that ensures + // that it will never be greater then numerical value of the key also. + if(len >= 256) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + /* // If Length is same as key size then + // compare the data with key value - date should be less then key value. + if(len == 255) { + // TODO the assumption is that private key exponent value is considered here. + tmpVariables[0]= op.getKey(scratchPad,(short)0); + tmpVariables[0] = Util.arrayCompare( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0, tmpVariables[0]); + if(tmpVariables[0] >= 0){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } +*/ // copy input data to scratchpad. + //TODO the current jacrdsim implementation requires 255 bytes when using encryption with no pad + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)(255 - len),len); + len = (short)255; + }else{ + //copy input data to scratchpad. + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0,len); + } + len = op.getCipher().doFinal( + scratchPad, (short)0,len, KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + break; + case KMType.AES: + if(op.getBlockMode() == KMType.GCM){ + finishAesGCMOperation(op, scratchPad); + return; + } + case KMType.DES: + if(op.getAlgorithm() == KMType.AES){ + tmpVariables[0] = KMCipher.AES_BLOCK_SIZE; + }else{ + tmpVariables[0] = KMCipher.DES_BLOCK_SIZE; + } + //If no padding then data length must be block aligned + if (op.getPadding() == KMType.PADDING_NONE && ((short)(len % tmpVariables[0]) != 0)){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + //If padding i.e. pkcs7 then add padding to right + if(op.getPadding() == KMType.PKCS7){ + // padding bytes + if(len % tmpVariables[0] == 0) tmpVariables[1] = tmpVariables[0]; + else tmpVariables[1] = (short)(tmpVariables[0] - (len % tmpVariables[0])); + // final len with padding + len = (short)(len+tmpVariables[1]); + // intermediate buffer to copy input data+padding + tmpVariables[2] = KMByteBlob.instance(len); + // fill in the padding + Util.arrayFillNonAtomic( + KMByteBlob.cast(tmpVariables[2]).getBuffer(), + KMByteBlob.cast(tmpVariables[2]).getStartOff(), + KMByteBlob.cast(tmpVariables[2]).length(), + (byte)tmpVariables[1]); + //copy the input data + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(tmpVariables[2]).getBuffer(), + KMByteBlob.cast(tmpVariables[2]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + data[INPUT_DATA] = tmpVariables[2]; + } + data[OUTPUT_DATA] = KMByteBlob.instance(len); + len = op.getCipher().doFinal( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + break; + } + } + + private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch(op.getAlgorithm()){ + // Only supported for testing purpose + // TODO remove this later on + case KMType.RSA: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + if(op.getPadding() == KMType.PADDING_NONE && + len != 256) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + len = op.getCipher().doFinal( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, scratchPad, + (short)0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad,(short)0, len); + break; + case KMType.AES: + if(op.getBlockMode() == KMType.GCM){ + finishAesGCMOperation(op, scratchPad); + return; + } + case KMType.DES: + if(op.getAlgorithm() == KMType.AES){ + tmpVariables[0] = KMCipher.AES_BLOCK_SIZE; + }else{ + tmpVariables[0] = KMCipher.DES_BLOCK_SIZE; + } + if((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.ECB)&& len > 0 && + (len%tmpVariables[0]) != 0)KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + tmpVariables[1] = repository.alloc(len); + byte[] heap = repository.getHeap(); + len = op.getCipher().doFinal( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), len,heap, + tmpVariables[1]); + //remove padding bytes if pkcs7 + if(op.getPadding() == KMType.PKCS7 && len >0) len = (short)(len - heap[(short)(tmpVariables[1]+len -1)]); + //If padding i.e. pkcs7 then add padding to right + data[OUTPUT_DATA] = KMByteBlob.instance(heap, tmpVariables[1], len); + break; + } + } + + private void finishAesGCMOperation(KMOperationState op, byte[] scratchPad) { + } + + private void finishSigningVerifyingOperation(KMOperationState op, byte[]scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch(op.getAlgorithm()){ + case KMType.RSA: + data[OUTPUT_DATA] = KMByteBlob.instance((short)256); + // No digest and no padding - This case is not supported in javacard api + // TODO confirm whether this case should be handled as it cannot be supported or tested + if(op.getDigest() == KMType.DIGEST_NONE && op.getPadding() == KMType.PADDING_NONE){ + if(op.getPurpose() == KMType.SIGN){ + // Length cannot be greater then key size + if(len > 256) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + // If Length is same as key size then + // compare the data with key value - date should be less then key value. + if(len == 256) { + // TODO the assumption is that private key exponent value is considered here. + tmpVariables[0]= op.getKey(scratchPad,(short)0); + tmpVariables[0] = Util.arrayCompare( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0, tmpVariables[0]); + if(tmpVariables[0] >= 0){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + }else{//Verify + if(len != 256) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + // Everything is fine so copy input data to scratchpad. + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)(256 - len),len); + len = (short)256; + } + // If PKCS1 padding and no digest - then 0x01||0x00||PS||0x00 on left such that PS = 8 bytes + // TODO confirm whether this case should be handled as it cannot be supported or tested + if (op.getDigest() == KMType.DIGEST_NONE && op.getPadding() == KMType.RSA_PKCS1_1_5_SIGN) { + // Length cannot be greater then 256 -11 = 245 bytes + if(len > 245){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + scratchPad[0] = 0x00; + scratchPad[1] = 0x01; + cryptoProvider.newRandomNumber(scratchPad, (short)2, (short)8); + scratchPad[10] = 0x00; + //copy the rest of the data on scratch pad. + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)11,len); + len += (short)11; + } + // Normal case with PKCS1 or PSS padding and with Digest SHA256 + if(op.getDigest()==KMType.SHA2_256 && + (op.getPadding() == KMType.RSA_PKCS1_1_5_SIGN ||op.getPadding() == KMType.RSA_PSS)){ + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + // Copy the data on the scratch pad. + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0,len); + } + if(op.getPurpose() == KMType.SIGN){ + // len of signature will be 256 bytes + len = op.getSignerVerifier().sign(scratchPad,(short)0,len, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + }else{ + if(!op.getSignerVerifier().verify(scratchPad,(short)0,len, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + break; + case KMType.EC: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + // If DIGEST NONE then truncate the data to 32 bytes. + // TODO Confirm whether this case needs to be supported as javacard does not support. + if(op.getDigest() == KMType.DIGEST_NONE || len > 32){ + len = 32; + } + if(op.getPurpose() == KMType.SIGN){ + // len of signature will be 512 bits i.e. 64 bytes + len = op.getSignerVerifier().sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),len, + scratchPad,(short)0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad,(short)0, len); + }else{ + if(!op.getSignerVerifier().verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),len, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + break; + case KMType.HMAC: + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + // digest is always present. + if(op.getPurpose() == KMType.SIGN){ + // len of signature will always be 32 bytes. + len = op.getSignerVerifier().sign(KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),len,scratchPad, + (short)0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad,(short)0, len); + }else{ + if(!op.getSignerVerifier().verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),len, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } break; + default:// This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } + } + + private void finishTrustedConfirmationOperation(KMOperationState op) { + // Perform trusted confirmation if required + if (op.isTrustedConfirmationRequired()) { + tmpVariables[0] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CONFIRMATION_TOKEN, data[KEY_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMByteTag.cast(tmpVariables[0]).getValue(); + boolean verified = op.getTrustedConfirmationSigner() + .verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + /* + if(tmpVariables[1] != KMByteBlob.cast(tmpVariables[0]).length() ){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + tmpVariables[0]=Util.arrayCompare(scratchPad,(short)0, + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + tmpVariables[1]);*/ + if(!verified){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + } + + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // User Authentication + if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { + validateVerificationToken(op, data[VERIFICATION_TOKEN], scratchPad); + tmpVariables[0] = KMInteger.uint_64(op.getAuthTime(), (short) 0); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if(op.isAuthPerOperationReqd()){ // Auth per operation + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + authorizeHwAuthToken(scratchPad); + } + } + + private void validateVerificationToken(KMOperationState op, short verToken, byte[] scratchPad) { + // CBOR Encoding is always big endian and Java is big endian + short ptr = KMVerificationToken.cast(verToken).getMac(); + short len = 0; + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return; + } + // validate operation handle. + ptr = KMVerificationToken.cast(verToken).getChallenge(); + if(op.getHandle() != KMInteger.cast(ptr).getShort()){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + // concatenation length will be 37 + length of verified parameters list. + short params = KMVerificationToken.cast(verToken).getParametersVerified(); + Util.arrayFillNonAtomic(scratchPad, (short) 0, + (short) (37+KMByteBlob.cast(params).length()), (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopy(authVerification,(short)0, scratchPad, (short)0, (short)authVerification.length); + len = (short)authVerification.length; + // concatenate challenge - 8 bytes + ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate security level - 4 bytes + ptr = KMVerificationToken.cast(verToken).getSecurityLevel(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += 4; + // concatenate Parameters verified - blob of encoded data. + ptr = KMVerificationToken.cast(verToken).getParametersVerified(); + len += KMByteBlob.cast(ptr).getValues(scratchPad, (short)0); + len += 4; + // hmac the data + HMACKey key = + cryptoProvider.createHMACKey( + repository.getComputedHmacKey(), + (short) 0, + (short) repository.getComputedHmacKey().length); + ptr = KMVerificationToken.cast(verToken).getMac(); + boolean verified = + cryptoProvider.hmacVerify(key, scratchPad, (short) 0, len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + if(!verified){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + /* + + // Compare mac. + ptr = KMVerificationToken.cast(verToken).getMac(); + if (macLen != KMByteBlob.cast(ptr).length()) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (Util.arrayCompare( + scratchPad, (short) (len+1), + KMByteBlob.cast(ptr).getBuffer(), KMByteBlob.cast(ptr).getStartOff(), macLen) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + */ + + } + + private void processUpdateOperationCmd(APDU apdu) { + // TODO Add Support for AES-GCM + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 5); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, KMInteger.exp()); + KMArray.cast(tmpVariables[1]).add((short) 1, tmpVariables[2]); + KMArray.cast(tmpVariables[1]).add((short) 2, KMByteBlob.exp()); + tmpVariables[3] = KMHardwareAuthToken.exp(); + KMArray.cast(tmpVariables[1]).add((short) 3, tmpVariables[3]); + tmpVariables[4] = KMVerificationToken.exp(); + KMArray.cast(tmpVariables[1]).add((short) 4, tmpVariables[4]); + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[OP_HANDLE] = KMArray.cast(tmpVariables[2]).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 1); + data[INPUT_DATA] = KMArray.cast(tmpVariables[2]).get((short) 2); + data[HW_TOKEN] = KMArray.cast(tmpVariables[2]).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(tmpVariables[2]).get((short) 4); + // Check Operation Handle and get op state + // Check Operation Handle + tmpVariables[1] = KMInteger.cast(data[OP_HANDLE]).getShort(); + KMOperationState op = repository.findOperation(tmpVariables[1]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + // If signing without digest then do length validation checks + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY ) { + // update the data. + op.getSignerVerifier() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + // update trusted confirmation operation + updateTrustedConfirmationOperation(op); + data[OUTPUT_DATA] = KMType.INVALID_VALUE; + } + if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT){ + // TODO Update for encrypt/decrypt using RSA will not be supported because to do this op state + // will have to buffer the data - so reject the update if it is rsa algorithm. + if(op.getAlgorithm() == KMCipher.CIPHER_RSA) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + if (op.getAlgorithm() == KMType.AES) { + // input data must be block aligned. + // 128 bit block size - HAL must send block aligned data + if (tmpVariables[0] % AES_BLOCK_SIZE != 0 || tmpVariables[0] <=0) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + if (op.getAlgorithm() == KMType.DES) { + // 64 bit block size - HAL must send block aligned data + if (tmpVariables[0] % DES_BLOCK_SIZE != 0) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + // Otherwise just update the data. + tmpVariables[0] = + op.getCipher() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + } + // make response + tmpVariables[1] = KMArray.instance((short) 0); + tmpVariables[1] = KMKeyParameters.instance(tmpVariables[1]); + tmpVariables[2] = KMArray.instance((short) 4); + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + KMArray.cast(tmpVariables[2]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[2]).add((short) 1, KMInteger.uint_16(tmpVariables[0])); + KMArray.cast(tmpVariables[2]).add((short) 2, tmpVariables[1]); + KMArray.cast(tmpVariables[2]).add((short) 3, data[OUTPUT_DATA]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[2], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void updateTrustedConfirmationOperation(KMOperationState op) { + if (op.isTrustedConfirmationRequired()) { + op.getTrustedConfirmationSigner() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } + } + + private void processBeginOperationCmd(APDU apdu) { + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + short args = KMType.INVALID_VALUE; + tmpVariables[1] = KMArray.instance((short) 4); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.cast(tmpVariables[1]).add((short) 1, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add((short) 2, tmpVariables[2]); + tmpVariables[3] = KMHardwareAuthToken.exp(); + KMArray.cast(tmpVariables[1]).add((short) 3, tmpVariables[3]); + // Decode the arguments + args = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 2); + data[KEY_BLOB] = KMArray.cast(args).get((short) 1); + // Check for app id and app data. + data[APP_ID] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + data[APP_DATA] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + if (data[APP_ID] != KMTag.INVALID_VALUE) { + data[APP_ID] = KMByteTag.cast(data[APP_ID]).getValue(); + } + if (data[APP_DATA] != KMTag.INVALID_VALUE) { + data[APP_DATA] = KMByteTag.cast(data[APP_DATA]).getValue(); + } + // Parse the encrypted blob and decrypt it. + parseEncryptedKeyBlob(scratchPad); + // Authorize the begin operation and reserve op - data[OP_HANDLE] will have the handle. + // It will also set data[IV] field if required. + tmpVariables[0] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMEnum.cast(tmpVariables[0]).getVal(); + data[HW_TOKEN] = KMArray.cast(args).get((short) 3); + KMOperationState op = repository.reserveOperation(); + if(op == null) KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + data[OP_HANDLE] = op.getHandle(); + op.setPurpose(tmpVariables[0]); + authorizeAndBeginOperation(op, scratchPad); + switch (op.getPurpose()){ + case KMType.SIGN: + beginTrustedConfirmationOperation(op); + case KMType.VERIFY: + beginSignVerifyOperation(op); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + beginCipherOperation(op); + break; + default: + KMException.throwIt(KMError.UNIMPLEMENTED); + break; + } + // If the data[IV] is required to be returned. + if (data[IV] != KMType.INVALID_VALUE) { + // TODO confirm why this is needed + tmpVariables[2] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[2]).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + } else { + tmpVariables[2] = KMArray.instance((short) 0); + } + tmpVariables[1] = KMKeyParameters.instance(tmpVariables[2]); + tmpVariables[0] = KMArray.instance((short) 3); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, tmpVariables[1]); + KMArray.cast(tmpVariables[0]).add((short) 2, data[OP_HANDLE]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void beginTrustedConfirmationOperation(KMOperationState op) { + // Check for trusted confirmation - if required then set the signer in op state. + if (KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS]) != KMType.INVALID_VALUE) { + // get operation + // get the hmac key + if (repository.getComputedHmacKey() == null) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + // set the Hmac signer + op.setTrustedConfirmationSigner( + cryptoProvider.createHmacSignerVerifier(Signature.MODE_VERIFY, + MessageDigest.ALG_SHA_256, + repository.getComputedHmacKey(), + (short) 0, (short) repository.getComputedHmacKey().length)); + op.getTrustedConfirmationSigner().update(confirmationToken,(short)0,(short)confirmationToken.length); + } + } + + private void authorizeAlgorithm(KMOperationState op){ + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]); + if(alg == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + op.setAlgorithm((byte)alg); + } + private void authorizePurpose(KMOperationState op){ + if(!KMEnumArrayTag.contains(KMType.PURPOSE,op.getPurpose(),data[HW_PARAMETERS])){ + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } + + private void authorizeDigest(KMOperationState op){ + short digests = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]); + op.setDigest(KMType.DIGEST_NONE); + short param = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if(param != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(param).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + param = KMEnumArrayTag.cast(param).get((short)0); + if(!KMEnumArrayTag.cast(digests).contains(param)) KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + op.setDigest((byte)param); + } + switch(op.getAlgorithm()){ + case KMType.EC: + case KMType.HMAC: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + break; + default: + break; + } + } + private void authorizePadding(KMOperationState op){ + short paddings = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]); + op.setPadding(KMType.PADDING_NONE); + short param = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if(param != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(param).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + param = KMEnumArrayTag.cast(param).get((short)0); + if(!KMEnumArrayTag.cast(paddings).contains(param)) KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + switch (op.getAlgorithm()){ + case KMType.RSA: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + if((op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY)&& + param != KMType.PADDING_NONE && + param != KMType.RSA_PSS && + param != KMType.RSA_PKCS1_1_5_SIGN) KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + if((op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) && + param != KMType.PADDING_NONE && + param != KMType.RSA_OAEP && + param != KMType.RSA_PKCS1_1_5_ENCRYPT) KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + if (param == KMType.PADDING_NONE && op.getDigest() != KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if ((param == KMType.RSA_OAEP || param == KMType.RSA_PSS) + && op.getDigest() == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + op.setPadding((byte)param); + break; + case KMType.DES: + case KMType.AES: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + op.setPadding((byte)param); + break; + default: + break; + } + } + private void authorizeBlockModeAndMacLength(KMOperationState op){ + short param = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + if(param != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(param).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + param = KMEnumArrayTag.cast(param).get((short)0); + } + short macLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAC_LENGTH, data[KEY_PARAMETERS]); + switch (op.getAlgorithm()){ + case KMType.AES: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + if (param == KMType.GCM){ + if(op.getPadding() != KMType.PADDING_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (macLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + if (macLen % 8 != 0 || macLen > 128 || + macLen < KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + }else if(macLen != KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + break; + case KMType.DES: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_ARGUMENT); + break; + case KMType.HMAC: + if (macLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + if (macLen != 256 || + macLen < KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizeAlgorithm(op); + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + authorizeKeyUsageForCount(); + authorizeUserSecureIdAuthTimeout(op, scratchPad); + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail. + data[IV] = KMType.INVALID_VALUE; + tmpVariables[0] = + KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.CALLER_NONCE, data[HW_PARAMETERS]); + tmpVariables[1] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + if (tmpVariables[0] == KMType.INVALID_VALUE) { + if (tmpVariables[1] != KMType.INVALID_VALUE) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + } + if (tmpVariables[1] != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(tmpVariables[1]).getValue(); + } + // For symmetric decryption iv is required + if(op.getPurpose() == KMType.DECRYPT && data[IV] == KMType.INVALID_VALUE && + (op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.GCM)){ + KMException.throwIt(KMError.MISSING_NONCE); + } + } + + private void beginCipherOperation(KMOperationState op) { + short padding; + short alg = -1; + short purpose; + if(op.getPurpose() == KMType.ENCRYPT) purpose = KMCipher.MODE_ENCRYPT; + else purpose = KMCipher.MODE_DECRYPT; + + switch (op.getAlgorithm()) { + // Not required to be supported - supported for testing purpose + // TODO remove this later + case KMType.RSA: + if (op.getPadding() == KMType.RSA_PKCS1_1_5_ENCRYPT) padding = KMCipher.PAD_PKCS1; + else if(op.getPadding() == KMType.RSA_OAEP){ + padding = KMCipher.PAD_PKCS1_OAEP_SHA256; + } + else padding = KMCipher.PAD_NOPAD; + try { + if(purpose == KMCipher.MODE_DECRYPT){ + op.setKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + op.setCipher( + cryptoProvider.createRsaDecipher( + padding, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + }else{ + op.setKey( + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + op.setCipher( + cryptoProvider.createRsaCipher( + padding, + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.AES: + if (op.getBlockMode() == KMType.GCM) { + beginAesGCMOperation(op); + return; + } + case KMType.DES: + if (op.getPadding() == KMType.PADDING_NONE) { + padding = KMCipher.PAD_NOPAD; + } else { + padding = KMCipher.PAD_PKCS7; + } + if (op.getAlgorithm() == KMType.AES) { + if (op.getBlockMode() == KMType.CBC) { + alg = KMCipher.ALG_AES_BLOCK_128_CBC_NOPAD; + } else if (op.getBlockMode() == KMType.ECB) { + alg = KMCipher.ALG_AES_BLOCK_128_ECB_NOPAD; + data[IV] = KMType.INVALID_VALUE; + } else { + // data[CIPHER_ALGORITHM] = Cipher.CIPHER_AES_CTR; // Not supported in 3.0.5 + // TODO change this once we can test. + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + } else if (op.getAlgorithm() == KMType.DES) { + if (op.getBlockMode() == KMType.CBC) { + alg = KMCipher.ALG_DES_CBC_NOPAD; + if(data[IV] == KMType.INVALID_VALUE){ + data[IV] = KMByteBlob.instance((short)16); + cryptoProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length() + ); + } + + } else { + alg = KMCipher.ALG_DES_ECB_NOPAD; + data[IV] = KMType.INVALID_VALUE; + } + } else { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + op.setKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + try { + if (data[IV] != KMType.INVALID_VALUE) { + op.setCipher( + cryptoProvider.createSymmetricCipher( + alg, + purpose, + padding, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length())); + } else { + op.setCipher( + cryptoProvider.createSymmetricCipher( + alg, + purpose, + padding, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length())); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + + + private void beginSignVerifyOperation(KMOperationState op) { + short padding; + short digest; + short purpose;; + if(op.getPurpose() == KMType.SIGN) purpose = Signature.MODE_SIGN; + else purpose = Signature.MODE_VERIFY; + switch(op.getAlgorithm()){ + case KMType.RSA: + if(op.getDigest() == KMType.DIGEST_NONE) digest = MessageDigest.ALG_NULL; + else digest = MessageDigest.ALG_SHA_256; + if(op.getPadding() == KMType.PADDING_NONE) padding = KMCipher.PAD_NOPAD; + else if(op.getPadding() == KMType.RSA_PSS) padding = KMCipher.PAD_PKCS1_PSS; + else padding = KMCipher.PAD_PKCS1; + op.setKey(KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + try{ + if (op.getPurpose() == KMType.SIGN) { + op.setSignerVerifier( + cryptoProvider.createRsaSigner( + digest, + padding, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + }else{ + op.setSignerVerifier( + cryptoProvider.createRsaVerifier( + digest, + padding, + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } + }catch(CryptoException exp){ + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.EC: + if(op.getDigest() == KMType.DIGEST_NONE) digest = MessageDigest.ALG_NULL; + else digest = MessageDigest.ALG_SHA_256; + op.setKey(KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + try{ + if (op.getPurpose() == KMType.SIGN) { + op.setSignerVerifier( + cryptoProvider.createEcSigner( + digest, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length())); + }else{ + op.setSignerVerifier( + cryptoProvider.createEcVerifier( + digest, + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } + }catch(CryptoException exp){ + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.HMAC: + digest = MessageDigest.ALG_SHA_256; + op.setKey(KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + try{ + op.setSignerVerifier( + cryptoProvider.createHmacSignerVerifier( + purpose, digest, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length())); + }catch(CryptoException exp){ + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + } + + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { + short authTime; + // Authorize User Secure Id and Auth timeout + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + tmpVariables[0] = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + // check if hw token is empty - mac should not be empty. + tmpVariables[1] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getMac(); + if (KMByteBlob.cast(tmpVariables[1]).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + authTime = KMIntegerTag.cast(tmpVariables[0]).getValue(); + authorizeHwAuthToken(scratchPad); + op.setOneTimeAuthReqd(true); + authTime = addIntegers(authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp()); + op.setAuthTime(KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + op.setAuthTimeoutValidated(false); // auth time validation will happen in update or finish + } else { // auth per operation required + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + private void beginAesGCMOperation(KMOperationState op) { + short purpose; + data[OP_HANDLE] = KMType.INVALID_VALUE; + if (op.getPurpose() == KMType.ENCRYPT) { + purpose = KMCipher.MODE_ENCRYPT; + } else { + purpose = KMCipher.MODE_DECRYPT; + } + if (op.getPadding() != KMType.PADDING_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + op.setKey(KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + op.setCipher( + cryptoProvider.createGCMCipher( + purpose, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length())); + data[OP_HANDLE] = op.getHandle(); } - /** Setter for keymaster state. */ - private void setKeymasterState(byte keymasterState) { - this.keymasterState = keymasterState; + + private void authorizeHwAuthToken(byte[] scratchPad) { + validateHwToken(data[HW_TOKEN], scratchPad); + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getUserId(); + if (KMInteger.cast(tmpVariables[0]).isZero()) { + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getAuthenticatorId(); + if (KMInteger.cast(tmpVariables[0]).isZero()) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + // check user secure id + if (!KMIntegerArrayTag.contains(KMType.USER_SECURE_ID, tmpVariables[0], data[HW_PARAMETERS])) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + // check auth type + tmpVariables[1] = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]); + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) tmpVariables[1]) == 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + + private void validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + short len = 0; + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return; + } + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 37, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(tmpVariables[0]) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(tmpVariables[0]) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += 4; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(tmpVariables[0]) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // hmac the data + HMACKey key = + cryptoProvider.createHMACKey( + repository.getComputedHmacKey(), + (short) 0, + (short) repository.getComputedHmacKey().length); + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + boolean verified = + cryptoProvider.hmacVerify(key, scratchPad, (short) 0, len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + if(!verified){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } +/* + len = + cryptoProvider.hmac(key, scratchPad, (short) 0, len, scratchPad, (short) (len + 1) ); + // Compare mac. + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + if (len != KMByteBlob.cast(ptr).length()) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (Util.arrayCompare( + scratchPad, + (short) 38, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + len) + != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + */ + } + + private void authorizeKeyUsageForCount() { + // TODO currently only short usageLimit supported - max count 32K. + short usageLimit = KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, data[HW_PARAMETERS]); + if (usageLimit == KMType.INVALID_VALUE) return; + // get current counter + short usage = repository.getRateLimitedKeyCount(data[AUTH_TAG]); + if (usage != KMType.INVALID_VALUE) { + if(usage < usageLimit){ + KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED); + } + // increment the counter and store it back. + usage++; + repository.setRateLimitedKeyCount(data[AUTH_TAG], usage); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + private void processImportKeyCmd(APDU apdu) { + if (repository.keyBlobCount > repository.MAX_BLOB_STORAGE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[1] = KMArray.instance((short) 3); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, tmpVariables[2]); + KMArray.cast(tmpVariables[1]).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.cast(tmpVariables[1]).add((short) 2, KMByteBlob.exp()); + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 0); + tmpVariables[3] = KMArray.cast(tmpVariables[2]).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(tmpVariables[2]).get((short) 2); + // Key format must be RAW format - X509 and PKCS8 not implemented. + tmpVariables[3] = KMEnum.cast(tmpVariables[3]).getVal(); + if (tmpVariables[3] != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + data[ORIGIN] = KMType.IMPORTED; + importKey(apdu, scratchPad); + } + + private void importKey(APDU apdu, byte[] scratchPad) { + // Bootloader only not supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + + // get algorithm + tmpVariables[3] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[3] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidDigests((byte)tmpVariables[3])){ + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidPaddingModes((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidPurpose((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidBlockMode((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMIntegerTag.cast(tmpVariables[4]).isValidKeySize((byte)tmpVariables[3])){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + // Check algorithm and dispatch to appropriate handler. + switch (tmpVariables[3]) { + case KMType.RSA: + importRSAKey(scratchPad); + break; + case KMType.AES: + importAESKey(scratchPad); + break; + case KMType.DES: + importTDESKey(scratchPad); + break; + case KMType.HMAC: + importHmacKey(scratchPad); + break; + case KMType.EC: + importECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + // create key blob + createEncryptedKeyBlob(scratchPad); + // persist auth tag for rollback resistance. + repository.persistAuthTag(data[AUTH_TAG]); + // prepare the response + tmpVariables[0] = KMArray.instance((short) 3); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, data[KEY_BLOB]); + KMArray.cast(tmpVariables[0]).add((short) 2, data[KEY_CHARACTERISTICS]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private void importECKeys(byte[] scratchPad) { + // Decode key material + tmpVariables[0] = KMArray.instance((short) 3); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret + KMArray.cast(tmpVariables[0]).add((short) 1, KMByteBlob.exp()); // public key + KMArray.cast(tmpVariables[0]).add((short) 2, KMEnumTag.exp()); // curve + tmpVariables[0] = + decoder.decode( + tmpVariables[0], + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length()); + data[SECRET] = KMArray.cast(tmpVariables[0]).get((short) 0); + data[PUB_KEY] = KMArray.cast(tmpVariables[0]).get((short) 1); + tmpVariables[1] = KMArray.cast(tmpVariables[0]).get((short) 2); + tmpVariables[1] = KMEnumTag.cast(tmpVariables[1]).getValue(); + // curve must be P_256 + if (tmpVariables[1] != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + // initialize 256 bit p256 key for given private key and public key. + ECPrivateKey ecKey = + cryptoProvider.createEcKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[4] = 0; // index for update list in scratchPad + // check whether the keysize tag is present in key parameters. + tmpVariables[2] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMType.INVALID_VALUE) { + if (tmpVariables[2] != 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + tmpVariables[5] = KMInteger.uint_16((short) 256); + tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + // check the curve if present in key parameters. + tmpVariables[3] = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if (tmpVariables[3] != KMType.INVALID_VALUE) { + if (tmpVariables[3] != tmpVariables[1]) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the curve to scratchPad + tmpVariables[5] = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[5]); + tmpVariables[4] += 2; + } + // add scratch pad to key parameters + updateKeyParameters(scratchPad, tmpVariables[4]); + // validate updated key parameters. + validateECKeys(scratchPad); + data[KEY_BLOB] = KMArray.instance((short) 5); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void importHmacKey(byte[] scratchPad) { + // Get Key + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret + tmpVariables[0] = + decoder.decode( + tmpVariables[0], + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length()); + data[SECRET] = KMArray.cast(tmpVariables[0]).get((short) 0); + // create HMAC key of up to 512 bit + HMACKey hmacKey = + cryptoProvider.createHMACKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[4] = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + tmpVariables[2] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMType.INVALID_VALUE) { + if (!(tmpVariables[2] >= 64 && tmpVariables[2] <= 512 && tmpVariables[2] % 8 == 0)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + tmpVariables[5] = KMInteger.uint_16(KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + // update the key parameters list + updateKeyParameters(scratchPad, tmpVariables[4]); + // validate HMAC Key parameters + validateHmacKey(scratchPad); + + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private void importTDESKey(byte[] scratchPad) { + // Decode Key Material + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret + tmpVariables[0] = + decoder.decode( + tmpVariables[0], + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length()); + data[SECRET] = KMArray.cast(tmpVariables[0]).get((short) 0); + DESKey desKey = + cryptoProvider.createTDESKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[4] = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + tmpVariables[2] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMType.INVALID_VALUE) { + if (tmpVariables[2] != 168) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + tmpVariables[5] = KMInteger.uint_16((short) 168); + tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + // update the key parameters list + updateKeyParameters(scratchPad, tmpVariables[4]); + // validate TDES Key parameters + validateTDESKey(scratchPad); + + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private void importAESKey(byte[] scratchPad) { + // Get Key + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret + tmpVariables[0] = + decoder.decode( + tmpVariables[0], + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length()); + data[SECRET] = KMArray.cast(tmpVariables[0]).get((short) 0); + // create 128 or 256 bit AES key + AESKey aesKey = + cryptoProvider.createAESKey( + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[4] = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + tmpVariables[2] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMType.INVALID_VALUE) { + if (tmpVariables[2] != 128 && tmpVariables[2] != 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratch pad + // add the key size to scratchPad + tmpVariables[5] = KMInteger.uint_16(KMByteBlob.cast(data[SECRET]).length()); + tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + // update the key parameters list + updateKeyParameters(scratchPad, tmpVariables[4]); + // validate AES Key parameters + validateAESKey(scratchPad); + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private void importRSAKey(byte[] scratchPad) { + // Decode key material + tmpVariables[0] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret = private exponent + KMArray.cast(tmpVariables[0]).add((short) 1, KMByteBlob.exp()); // modulus + tmpVariables[0] = + decoder.decode( + tmpVariables[0], + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[IMPORTED_KEY_BLOB]).length()); + data[SECRET] = KMArray.cast(tmpVariables[0]).get((short) 0); + data[PUB_KEY] = KMArray.cast(tmpVariables[0]).get((short) 1); + tmpVariables[4] = 0; // index in scratchPad for update parameters. + // validate public exponent if present in key params - it must be 0x010001 + tmpVariables[2] = + KMIntegerTag.getValue( + scratchPad, + (short) 10, // using offset 10 as first 10 bytes reserved for update params + KMType.ULONG_TAG, + KMType.RSA_PUBLIC_EXPONENT, + data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMTag.INVALID_VALUE) { + if (tmpVariables[2] != 4 + || Util.getShort(scratchPad, (short) 10) != 0x01 + || Util.getShort(scratchPad, (short) 12) != 0x01) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } else { + // add public exponent to scratchPad + Util.setShort(scratchPad, (short) 10, (short) 0x01); + Util.setShort(scratchPad, (short) 12, (short) 0x01); + tmpVariables[5] = KMInteger.uint_32(scratchPad, (short) 10); + tmpVariables[6] = + KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + + // initialize 2048 bit private key for given private exp and modulus. + RSAPrivateKey rsaKey = + cryptoProvider.createRsaKey( + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + // check the keysize tag if present in key parameters. + tmpVariables[2] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[2] != KMType.INVALID_VALUE) { + if (tmpVariables[2] != 2048) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + tmpVariables[5] = KMInteger.uint_16((short) 2048); + tmpVariables[6] = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, tmpVariables[5]); + Util.setShort(scratchPad, tmpVariables[4], tmpVariables[6]); + tmpVariables[4] += 2; + } + // update the key parameters list + updateKeyParameters(scratchPad, tmpVariables[4]); + // validate RSA Key parameters + validateRSAKey(scratchPad); + data[KEY_BLOB] = KMArray.instance((short) 5); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void updateKeyParameters(byte[] ptrArr, short len) { + if (len == 0) { + return; // nothing to update + } + // Create Update Param array and copy current params + tmpVariables[0] = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals(); + tmpVariables[1] = (short) (KMArray.cast(tmpVariables[0]).length() + (short) (len / 2)); + tmpVariables[1] = KMArray.instance(tmpVariables[1]); // update params + tmpVariables[2] = KMArray.cast(tmpVariables[0]).length(); + tmpVariables[3] = 0; + // copy the existing key parameters to updated array + while (tmpVariables[3] < tmpVariables[2]) { + tmpVariables[4] = KMArray.cast(tmpVariables[0]).get(tmpVariables[3]); + KMArray.cast(tmpVariables[1]).add(tmpVariables[3], tmpVariables[4]); + tmpVariables[3]++; + } + // copy new parameters to updated array + tmpVariables[2] = KMArray.cast(tmpVariables[1]).length(); + tmpVariables[5] = 0; // index in ptrArr + while (tmpVariables[3] < tmpVariables[2]) { + tmpVariables[4] = Util.getShort(ptrArr, tmpVariables[5]); + KMArray.cast(tmpVariables[1]).add(tmpVariables[3], tmpVariables[4]); + tmpVariables[3]++; + tmpVariables[5] += 2; + } + // replace with updated key parameters. + data[KEY_PARAMETERS] = KMKeyParameters.instance(tmpVariables[1]); + } + + // TODO Add Signature verification. + private void processSetBootParamsCmd(APDU apdu) { + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Argument 1 OS Version + // short osVersionExp = KMIntegerTag.exp(KMType.UINT_TAG); + tmpVariables[0] = KMInteger.exp(); + // Argument 2 OS Patch level + // short osPatchExp = KMIntegerTag.exp(KMType.UINT_TAG); + tmpVariables[1] = KMInteger.exp(); + // Argument 3 Verified Boot Key + // short bootKeyExp = KMByteBlob.exp(); + tmpVariables[2] = KMByteBlob.exp(); + // Argument 4 Verified Boot Hash + // short bootHashExp = KMByteBlob.exp(); + tmpVariables[3] = KMByteBlob.exp(); + // Argument 5 Verified Boot State + // short bootStateExp = KMEnum.instance(KMType.VERIFIED_BOOT_STATE); + tmpVariables[4] = KMEnum.instance(KMType.VERIFIED_BOOT_STATE); + // Argument 6 Device Locked + // short deviceLockedExp = KMEnum.instance(KMType.DEVICE_LOCKED); + tmpVariables[5] = KMEnum.instance(KMType.DEVICE_LOCKED); + // Array of expected arguments + short argsProto = KMArray.instance((short) 6); + KMArray.cast(argsProto).add((short) 0, tmpVariables[0]); + KMArray.cast(argsProto).add((short) 1, tmpVariables[1]); + KMArray.cast(argsProto).add((short) 2, tmpVariables[2]); + KMArray.cast(argsProto).add((short) 3, tmpVariables[3]); + KMArray.cast(argsProto).add((short) 4, tmpVariables[4]); + KMArray.cast(argsProto).add((short) 5, tmpVariables[5]); + // Decode the arguments + //System.out.println("Process boot params buffer: "+byteArrayToHexString(buffer)); + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + // short osVersionTagPtr = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 0); + // short osPatchTagPtr = KMArray.cast(args).get((short) 1); + tmpVariables[1] = KMArray.cast(args).get((short) 1); + // short verifiedBootKeyPtr = KMArray.cast(args).get((short) 2); + tmpVariables[2] = KMArray.cast(args).get((short) 2); + // short verifiedBootHashPtr = KMArray.cast(args).get((short) 3); + tmpVariables[3] = KMArray.cast(args).get((short) 3); + // short verifiedBootStatePtr = KMArray.cast(args).get((short) 4); + tmpVariables[4] = KMArray.cast(args).get((short) 4); + // short deviceLockedPtr = KMArray.cast(args).get((short) 5); + tmpVariables[5] = KMArray.cast(args).get((short) 5); + if (KMByteBlob.cast(tmpVariables[2]).length() > repository.BOOT_KEY_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (KMByteBlob.cast(tmpVariables[3]).length() > repository.BOOT_HASH_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Begin transaction + JCSystem.beginTransaction(); + KMInteger.cast(tmpVariables[0]).value(repository.osVersion, (short) 0); + KMInteger.cast(tmpVariables[1]).value(repository.osPatch, (short) 0); + //KMInteger.cast(valPtr).getValue(repository.osVersion, (short) 0, (short) 4); + //valPtr = KMIntegerTag.cast(tmpVariables[1]).getValue(); + //KMInteger.cast(valPtr).getValue(repository.osPatch, (short) 0, (short) 4); + repository.actualBootKeyLength = KMByteBlob.cast(tmpVariables[2]).length(); + KMByteBlob.cast(tmpVariables[2]) + .getValue(repository.verifiedBootKey, (short) 0, repository.actualBootKeyLength); + repository.actualBootHashLength = KMByteBlob.cast(tmpVariables[3]).length(); + KMByteBlob.cast(tmpVariables[3]) + .getValue(repository.verifiedBootHash, (short) 0, repository.actualBootHashLength); + byte enumVal = KMEnum.cast(tmpVariables[4]).getVal(); + if (enumVal == KMTag.SELF_SIGNED_BOOT) { + repository.selfSignedBootFlag = true; + repository.verifiedBootFlag = false; + } else { + repository.selfSignedBootFlag = false; + repository.verifiedBootFlag = true; + } + enumVal = KMEnum.cast(tmpVariables[5]).getVal(); + if (enumVal == KMType.DEVICE_LOCKED_TRUE) { + repository.deviceLockedFlag = true; + } else { + repository.deviceLockedFlag = false; + } + if (keymasterState == KMKeymasterApplet.FIRST_SELECT_STATE) { + setBootParamsDone = true; + if (provisionDone) { + keymasterState = KMKeymasterApplet.ACTIVE_STATE; + } + } + // end transaction + JCSystem.commitTransaction(); + } + + private static void processGenerateKey(APDU apdu) { + // before generating key, check whether max count reached + if (repository.keyBlobCount > repository.MAX_BLOB_STORAGE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Receive the incoming request fully from the master into buffer. + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Argument + tmpVariables[0] = KMKeyParameters.exp(); + // Array of expected arguments + tmpVariables[1] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[1]).add((short) 0, tmpVariables[0]); + // Decode the argument + tmpVariables[2] = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + data[KEY_PARAMETERS] = KMArray.cast(tmpVariables[2]).get((short) 0); + // Bootloader only not supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // get algorithm + tmpVariables[3] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[3] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // validate digest - only digest none or 256 is supported. + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidDigests((byte)tmpVariables[3])){ + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidPaddingModes((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidPurpose((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMIntegerTag.cast(tmpVariables[4]).isValidKeySize((byte)tmpVariables[3])){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMEnumArrayTag.cast(tmpVariables[4]).isValidBlockMode((byte)tmpVariables[3])){ + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + tmpVariables[4] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE){ + if(!KMIntegerTag.cast(tmpVariables[4]).isValidKeySize((byte)tmpVariables[3])){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + // Check algorithm and dispatch to appropriate handler. + switch (tmpVariables[3]) { + case KMType.RSA: + generateRSAKey(scratchPad); + break; + case KMType.AES: + generateAESKey(scratchPad); + break; + case KMType.DES: + generateTDESKey(scratchPad); + break; + case KMType.HMAC: + generateHmacKey(scratchPad); + break; + case KMType.EC: + generateECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + // create key blob + data[ORIGIN] = KMType.GENERATED; + createEncryptedKeyBlob(scratchPad); + // persist auth tag for rollback resistance. + repository.persistAuthTag(data[AUTH_TAG]); + // prepare the response + tmpVariables[0] = KMArray.instance((short) 3); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(tmpVariables[0]).add((short) 1, data[KEY_BLOB]); + KMArray.cast(tmpVariables[0]).add((short) 2, data[KEY_CHARACTERISTICS]); + // Encode the response + bufferLength = encoder.encode(tmpVariables[0], buffer, bufferStartOffset); + sendOutgoing(apdu); + } + + private static void validateRSAKey(byte[] scratchPad) { + // Read key size + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[0] == KMTag.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (tmpVariables[0] != 2048) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read public exponent into scratch pad + tmpVariables[1] = + KMIntegerTag.getValue( + scratchPad, + (short) 0, + KMType.ULONG_TAG, + KMType.RSA_PUBLIC_EXPONENT, + data[KEY_PARAMETERS]); + if ((tmpVariables[1] == KMTag.INVALID_VALUE) || (tmpVariables[1] != 4)) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Only exponent support is F4 - 65537 which is 0x00010001. + if (Util.getShort(scratchPad, (short) 0) != 0x01 + || Util.getShort(scratchPad, (short) 2) != 0x01) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + // Generate key handlers + private static void generateRSAKey(byte[] scratchPad) { + // Validate RSA Key + validateRSAKey(scratchPad); + // Now generate 2048 bit RSA keypair for the given exponent + KeyPair rsaKey = cryptoProvider.createRsaKeyPair(); + // store the pub exponent + data[RSA_PUB_EXPONENT] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[1]); + // extract modulus + tmpVariables[0] = ((RSAPrivateKey) rsaKey.getPrivate()).getModulus(scratchPad, (short) 0); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + // extract private key + tmpVariables[0] = ((RSAPrivateKey) rsaKey.getPrivate()).getExponent(scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + data[KEY_BLOB] = KMArray.instance((short) 5); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateAESKey(byte[] scratchPad) { + // Read key size + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[0] == KMTag.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if ((tmpVariables[0] != 256) && (tmpVariables[0] != 128)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Block mode - array of byte values + tmpVariables[1] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + if (tmpVariables[1] != KMTag.INVALID_VALUE) { // block mode specified + // Find Minimum Mac length + tmpVariables[2] = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + // If block modes contain GCM mode + if(KMEnumArrayTag.cast(tmpVariables[1]).contains(KMType.GCM)){ + // minimum mac length must be specified + if (tmpVariables[2] == KMTag.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MIN_MAC_LENGTH); + } + tmpVariables[3] = KMIntegerTag.cast(tmpVariables[2]).getValue(); + // Validate the MIN_MAC_LENGTH for AES - should be multiple of 8, less then 128 bits + // and greater the 96 bits + if(KMInteger.cast(tmpVariables[3]).getSignificantShort() != 0 || + KMInteger.cast(tmpVariables[3]).getShort() > 128 || + KMInteger.cast(tmpVariables[3]).getShort() < 96 || + (KMInteger.cast(tmpVariables[3]).getShort() % 8) != 0){ + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + }else{ // No GCM mode then no minimum mac length must be specified + if (tmpVariables[2] != KMTag.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + } + } + + private static void generateAESKey(byte[] scratchPad) { + validateAESKey(scratchPad); + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + AESKey aesKey = cryptoProvider.createAESKey(tmpVariables[0]); + tmpVariables[0] = aesKey.getKey(scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private static void validateECKeys(byte[] scratchPad) { + // Read key size + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + tmpVariables[1] = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if ((tmpVariables[0] == KMTag.INVALID_VALUE) && (tmpVariables[1] == KMType.INVALID_VALUE)){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + }else if((tmpVariables[1] != KMTag.INVALID_VALUE) && (tmpVariables[1] != KMType.P_256)){ + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + }else if ((tmpVariables[0] != KMTag.INVALID_VALUE) && (tmpVariables[0] != (short)256)){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateECKeys(byte[] scratchPad) { + validateECKeys(scratchPad); + KeyPair ecKey = cryptoProvider.createECKeyPair(); + tmpVariables[5] = ((ECPublicKey) ecKey.getPublic()).getW(scratchPad, (short) 0); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[5]); + tmpVariables[5] = ((ECPrivateKey) ecKey.getPrivate()).getS(scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[5]); + data[KEY_BLOB] = KMArray.instance((short) 5); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateTDESKey(byte[] scratchPad) { + // Read Minimum Mac length - it must not be present + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_TAG); + } + // Read keysize + tmpVariables[1] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[1] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (tmpVariables[1] != 168 && tmpVariables[1] != 192) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateTDESKey(byte[] scratchPad) { + validateTDESKey(scratchPad); + DESKey desKey = cryptoProvider.createTDESKey(); + tmpVariables[0] = desKey.getKey(scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private static void validateHmacKey(byte[] scratchPad) { + // check whether digest sizes are greater then or equal to min mac length. + // Only SHA256 digest must be supported. + if(KMEnumArrayTag.contains(KMType.DIGEST, KMType.DIGEST_NONE, data[KEY_PARAMETERS])){ + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + // Read Minimum Mac length + tmpVariables[0] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + if (tmpVariables[0] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MIN_MAC_LENGTH); + } + if (((short) (tmpVariables[0] % 8) != 0) || + (tmpVariables[0] < (short) 64)|| + tmpVariables[0] > (short)256) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + // Read keysize + tmpVariables[1] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (tmpVariables[1] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (!(tmpVariables[1] >= 64 && tmpVariables[1] <= 512 && tmpVariables[1] % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateHmacKey(byte[] scratchPad) { + validateHmacKey(scratchPad); + tmpVariables[1] = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + // generate HMAC Key + HMACKey hmacKey = cryptoProvider.createHMACKey(tmpVariables[1]); + tmpVariables[0] = hmacKey.getKey(scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[0]); + data[KEY_BLOB] = KMArray.instance((short) 4); + } + + private void checkVersionAndPatchLevel(byte[] scratchPad) { + tmpVariables[0] = + KMIntegerTag.getValue( + scratchPad, (short) 0, KMType.UINT_TAG, KMType.OS_VERSION, data[HW_PARAMETERS]); + if ((tmpVariables[0] != KMType.INVALID_VALUE) + && (Util.arrayCompare( + repository.osVersion, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + != 0)) { + if (Util.arrayCompare(repository.osVersion, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + == -1) { + // If the key characteristics has os version > current os version + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } else { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + } + tmpVariables[0] = + KMIntegerTag.getValue( + scratchPad, (short) 0, KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, data[HW_PARAMETERS]); + if ((tmpVariables[0] != KMType.INVALID_VALUE) + && (Util.arrayCompare(repository.osPatch, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + != 0)) { + if (Util.arrayCompare(repository.osPatch, (short) 0, scratchPad, (short) 0, tmpVariables[0]) + == -1) { + // If the key characteristics has os patch level > current os patch + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } else { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + } + } + + private static void makeKeyCharacteristics(byte[] scratchPad) { + tmpVariables[0] = + KMInteger.instance(repository.osPatch, (short) 0, (short) repository.osPatch.length); + tmpVariables[1] = + KMInteger.instance(repository.osVersion, (short) 0, (short) repository.osVersion.length); + data[HW_PARAMETERS] = + KMKeyParameters.makeHwEnforced( + data[KEY_PARAMETERS], + (byte) data[ORIGIN], + tmpVariables[1], + tmpVariables[0], + scratchPad); + data[SW_PARAMETERS] = KMKeyParameters.makeSwEnforced(data[KEY_PARAMETERS], scratchPad); + data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setHardwareEnforced(data[HW_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setSoftwareEnforced(data[SW_PARAMETERS]); + } + + private static void createEncryptedKeyBlob(byte[] scratchPad) { + // make key characteristics - returns key characteristics in data[KEY_CHARACTERISTICS] + makeKeyCharacteristics(scratchPad); + // make root of trust blob + data[ROT] = + KMByteBlob.instance( + repository.verifiedBootKey, (short) 0, (short) repository.verifiedBootKey.length); + // make hidden key params list + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad); + // make authorization data + makeAuthData(scratchPad); + // encrypt the secret and cryptographically attach that to authorization data + encryptSecret(scratchPad); + + // create key blob array + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_NONCE, data[NONCE]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_KEYCHAR, data[KEY_CHARACTERISTICS]); + tmpVariables[0] = repository.alloc((short) 1024); // TODO use buffer + tmpVariables[1] = encoder.encode(data[KEY_BLOB], repository.getHeap(), tmpVariables[0]); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), tmpVariables[0], tmpVariables[1]); + } + + private static void parseEncryptedKeyBlob(byte[] scratchPad) { + tmpVariables[0] = KMByteBlob.cast(data[KEY_BLOB]).getStartOff(); + tmpVariables[1] = KMArray.instance((short) 5); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_SECRET, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, KMByteBlob.exp()); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_NONCE, KMByteBlob.exp()); + tmpVariables[2] = KMKeyCharacteristics.exp(); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_KEYCHAR, tmpVariables[2]); + KMArray.cast(tmpVariables[1]).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, KMByteBlob.exp()); + data[KEY_BLOB] = + decoder.decodeArray( + tmpVariables[1], + KMByteBlob.cast(data[KEY_BLOB]).getBuffer(), + KMByteBlob.cast(data[KEY_BLOB]).getStartOff(), + KMByteBlob.cast(data[KEY_BLOB]).length()); + tmpVariables[0] = KMArray.cast(data[KEY_BLOB]).length(); + if (tmpVariables[0] < 4) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Validate Auth Tag + data[AUTH_TAG] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_AUTH_TAG); + if (!repository.validateAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // initialize data + data[NONCE] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_NONCE); + data[SECRET] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET); + data[KEY_CHARACTERISTICS] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_KEYCHAR); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (tmpVariables[0] == 5) { + data[PUB_KEY] = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY); + } + data[HW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getHardwareEnforced(); + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getSoftwareEnforced(); + // make root of trust blob + data[ROT] = + KMByteBlob.instance( + repository.verifiedBootKey, (short) 0, (short) repository.verifiedBootKey.length); + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[APP_ID], data[APP_DATA], data[ROT], scratchPad); + // make auth data + makeAuthData(scratchPad); + // Decrypt Secret and verify auth tag + decryptSecret(scratchPad); + } + + private static void decryptSecret(byte[] scratchPad) { + // derive master key - stored in derivedKey + tmpVariables[0] = deriveKey(scratchPad); + AESKey derivedKey = + cryptoProvider.createAESKey(repository.getHeap(), data[DERIVED_KEY], tmpVariables[0]); + if (derivedKey == null) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + boolean verification = + cryptoProvider.aesGCMDecrypt( + derivedKey, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + if (verification != true) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Copy the decrypted secret + data[SECRET] = + KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); + } + + private static void encryptSecret(byte[] scratchPad) { + // make nonce + data[NONCE] = KMByteBlob.instance((short) AES_GCM_NONCE_LENGTH); + data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).length()); + cryptoProvider.newRandomNumber( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length()); + // derive master key - stored in derivedKey + tmpVariables[0] = deriveKey(scratchPad); + AESKey derivedKey = + cryptoProvider.createAESKey(repository.getHeap(), data[DERIVED_KEY], tmpVariables[0]); + if (derivedKey == null) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + tmpVariables[1] = + cryptoProvider.aesGCMEncrypt( + derivedKey, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + if (tmpVariables[1] > 0) { + if (tmpVariables[1] != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + KMByteBlob.cast(data[SECRET]).setValue(scratchPad, (short) 0, tmpVariables[1]); + } + } + + private static void makeAuthData(byte[] scratchPad) { + tmpVariables[0] = + addPtrToAAD(KMKeyParameters.cast(data[HW_PARAMETERS]).getVals(), scratchPad, (short) 0); + tmpVariables[0] += + addPtrToAAD( + KMKeyParameters.cast(data[SW_PARAMETERS]).getVals(), scratchPad, tmpVariables[0]); + tmpVariables[0] += + addPtrToAAD( + KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals(), scratchPad, tmpVariables[0]); + // convert scratch pad to KMArray + tmpVariables[1] = KMArray.instance(tmpVariables[0]); + short index = 0; + short objPtr = 0; + while (index < tmpVariables[0]) { + objPtr = Util.getShort(scratchPad, (short) (index * 2)); + KMArray.cast(tmpVariables[1]).add(index, objPtr); + index++; + } + data[AUTH_DATA] = repository.alloc(MAX_AUTH_DATA_SIZE); + short len = encoder.encode(tmpVariables[1], repository.getHeap(), data[AUTH_DATA]); + data[AUTH_DATA_LENGTH] = len; + } + + private static short addPtrToAAD(short dataArrPtr, byte[] aadBuf, short offset) { + short index = (short) (offset * 2); + short tagInd = 0; + short tagPtr = 0; + short arrLen = KMArray.cast(dataArrPtr).length(); + while (tagInd < arrLen) { + tagPtr = KMArray.cast(dataArrPtr).get(tagInd); + Util.setShort(aadBuf, index, tagPtr); + index += 2; + tagInd++; + } + return tagInd; + } + + private static short deriveKey(byte[] scratchPad) { + tmpVariables[0] = KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals(); + tmpVariables[1] = repository.alloc((short) 256); + // generate derivation material from hidden parameters + tmpVariables[2] = encoder.encode(tmpVariables[0], repository.getHeap(), tmpVariables[1]); + // create derived key i.e. MAC + tmpVariables[3] = + cryptoProvider.aesCCMSign( + repository.getHeap(), + tmpVariables[1], + tmpVariables[2], + repository.getMasterKeySecret(), + scratchPad, + (short) 0); + if (tmpVariables[3] < 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // store the derived secret in data dictionary + data[DERIVED_KEY] = repository.alloc(tmpVariables[3]); + Util.arrayCopyNonAtomic( + scratchPad, (short) 0, repository.getHeap(), data[DERIVED_KEY], tmpVariables[3]); + return tmpVariables[3]; + } + + private static void sendError(APDU apdu, short err) { + bufferLength = encoder.encodeError(err, buffer, bufferStartOffset, (short) 5); + sendOutgoing(apdu); + } + + private short addIntegers(short num1, short num2){ + short buf = repository.alloc((short)24); + byte[] scratchPad = repository.getHeap(); + Util.arrayFillNonAtomic(scratchPad, buf, (short)24,(byte)0); + Util.arrayCopyNonAtomic( + KMInteger.cast(num1).getBuffer(), + KMInteger.cast(num1).getStartOff(),scratchPad,(short)(buf+8-KMInteger.cast(num1).length()), + KMInteger.cast(num1).length()); + Util.arrayCopyNonAtomic( + KMInteger.cast(num2).getBuffer(), + KMInteger.cast(num2).getStartOff(),scratchPad,(short)(buf+16-KMInteger.cast(num2).length()), + KMInteger.cast(num2).length()); + short index = 1; + short result = 0; + while(index <= 8){ + result = (short)(scratchPad[(short)(buf+8-index)] + scratchPad[(short)(buf+16-index)]+result); + scratchPad[(short)(buf+24-index)] = (byte)(result & 0xFF); + result = (short)(result >> 8); + index++; + } + return KMInteger.uint_64(scratchPad,(short)(buf+16)); + } +/* + private static void print (String lab, byte[] b, short s, short l){ + byte[] i = new byte[l]; + Util.arrayCopyNonAtomic(b,s,i,(short)0,l); + print(lab,i); + } + private static void print(String label, byte[] buf){ + System.out.println(label+": "); + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < buf.length; i++){ + sb.append(String.format(" 0x%02X", buf[i])) ; + if(((i-1)%38 == 0) && ((i-1) >0)){ + sb.append(";\n"); + } + } + System.out.println(sb.toString()); } +*/ } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMMessenger.java b/Applet/Applet/src/com/android/javacard/keymaster/KMMessenger.java deleted file mode 100644 index 3eb4b548..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMMessenger.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public interface KMMessenger { - void receiveIncoming(KMContext context); - void sendOutgoing(KMContext context); -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java index 5aaa9948..892c5d18 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -16,37 +16,176 @@ package com.android.javacard.keymaster; +import javacard.framework.Util; +import javacard.security.Signature; + // TODO complete the class design and implementation public class KMOperationState { - private KMInteger operationHandle; + private short opHandleCounter; + private byte algorithm; + private byte padding; + private byte blockMode; + private byte digest; - private KMOperationState() { - operationHandle = null; - } + private boolean active; + private boolean trustedConfirmation; + private boolean cipherOperation; + // TODO This should be 64 bits + private short handle; + private short purpose; + private KMCipher cipher; + private Signature hmacSigner; // used for trusted confirmation. + private Signature signer; + private byte[] key; + private short keyLength; + private byte[] authTime; + private boolean authPerOperationReqd; + private boolean secureUserIdReqd; + private boolean authTimeoutValidated; - public static KMOperationState instance(KMContext context) { - // TODO make operation handle - return context.getRepository().newOperationState(); + public KMOperationState(){ + authTime = new byte[8]; + key = new byte[256]; + reset(); } - public static void create(KMOperationState[] opStateRefTable) { - byte index = 0; - while (index < opStateRefTable.length) { - opStateRefTable[index] = new KMOperationState(); - index++; + public void setTrustedConfirmationSigner(Signature hmacSigner){ + this.hmacSigner = hmacSigner; + trustedConfirmation = true; + } + public Signature getTrustedConfirmationSigner(){ + return hmacSigner; + } + public boolean isTrustedConfirmationRequired(){ + return trustedConfirmation; + } + public void activate(){ + active = true; + handle = getOpHandleCounter(); + } + public void reset(){ + Util.arrayFillNonAtomic(authTime, (short)0,(short)8, (byte)0); + cipherOperation = false; + keyLength = 0; + authPerOperationReqd = false; + secureUserIdReqd = false; + active = false; + handle = 0; + Util.arrayFillNonAtomic(key,(short)0,(short)key.length,(byte)0); + cipher = null; + signer = null; + purpose = KMType.INVALID_VALUE; + trustedConfirmation = false; + hmacSigner = null; + authTimeoutValidated = false; + } + //TODO make this random number + public short getOpHandleCounter() { + opHandleCounter++; + if(opHandleCounter < 0){ + opHandleCounter = 0; } + return opHandleCounter; + } + + public boolean isActive() { + return active; + } + public boolean isCipherOperation(){ return cipherOperation;} + public void setCipherOperation(boolean flag){cipherOperation = flag;} + + public short getHandle() { + return KMInteger.uint_16(handle); + } + + public short handle(){ + return handle; + } + public short getPurpose() { + return purpose; + } + + public void setPurpose(short purpose) { + this.purpose = purpose; + } + + public KMCipher getCipher() { + return cipher; + } + + public void setCipher(KMCipher cipher) { + this.cipher = cipher; + } + + public Signature getSignerVerifier() { + return signer; + } + + public void setSignerVerifier(Signature signer) { + this.signer = signer; + } + + public short getKey(byte[] buf, short start) { + Util.arrayCopy(key,(short)0, buf, start,keyLength); + return keyLength; + } + + public void setKey(byte[] buf, short start, short len) { + keyLength = len; + Util.arrayCopy(buf, start, key, (short)0, len); + } + + public boolean isAuthPerOperationReqd() { + return authPerOperationReqd; + } + + public boolean isAuthTimeoutValidated() { + return authTimeoutValidated; + } + public boolean isSecureUserIdReqd(){return secureUserIdReqd;} + + public byte[] getAuthTime() { + return authTime; + } + + public void setAuthTime(byte[] time, short start) { + Util.arrayCopy(time, start, authTime, (short)0, (short)8); + } + public void setOneTimeAuthReqd(boolean flag){secureUserIdReqd = flag;} + public void setAuthTimeoutValidated(boolean flag) { + authTimeoutValidated = flag; + } + public void setAuthPerOperationReqd(boolean flag){ authPerOperationReqd = flag;} + + public byte getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(byte algorithm) { + this.algorithm = algorithm; + } + + public byte getPadding() { + return padding; + } + + public void setPadding(byte padding) { + this.padding = padding; + } + + public byte getBlockMode() { + return blockMode; } - public KMInteger getOperationHandle() { - return operationHandle; + public void setBlockMode(byte blockMode) { + this.blockMode = blockMode; } - public void setOperationHandle(KMInteger operationHandle) { - this.operationHandle = operationHandle; + public byte getDigest() { + return digest; } - public void release(KMContext context) { - // TODO release handle - context.getRepository().releaseOperationState(this); + public void setDigest(byte digest) { + this.digest = digest; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMProvisionCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMProvisionCmd.java deleted file mode 100644 index d1911f7e..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMProvisionCmd.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMProvisionCmd extends KMAbstractCmd { - public static final byte INS_PROVISION_CMD = 0x23; - - @Override - public byte getIns() { - return INS_PROVISION_CMD; - } - - @Override - public KMArray process(KMArray args, KMContext context) { - KMKeyParameters arg1 = (KMKeyParameters)args.get((short)0); - KMEnum arg2 = (KMEnum)args.get((short)1); - KMByteBlob arg3 = (KMByteBlob)args.get((short)2); - provision(arg1, arg2.getVal(),arg3); - context.setKeymasterState(KMKeymasterApplet.ACTIVE_STATE); - //nothing to return - return null; - } - - // TODO implement functionality - private void provision(KMKeyParameters params, byte keyFormat, KMByteBlob keyBlob){ - } - - @Override - protected boolean validateState(byte state) { - return (KMKeymasterApplet.FIRST_SELECT_STATE == state); - } - - // Uses import key command signature but does not return anything back. - protected KMArray getExpectedArgs() { - // Argument 1 - KMKeyParameters keyparams = KMKeyParameters.instance(); - // Argument 2 - KMEnum keyFormat = KMEnum.instance().setType(KMType.KEY_FORMAT); - // Argument 3 - KMByteBlob keyBlob = KMByteBlob.instance(); - // Array of expected arguments - return KMArray.instance((short) 3) - .add((short) 0, keyparams) - .add((short) 1, keyFormat) - .add((short) 2, keyBlob); - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java b/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java index 36d8f6f7..696b06f0 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -17,509 +17,291 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; -import javacard.security.AESKey; -import javacard.security.KeyBuilder; - -// TODO cleanup, move most of the buffers to transient memory with "clear on deselect". Only -// exception may be OperationState - TBD. The initialize and reset functions will be refactored -// to handle onInstall and onSelect. public class KMRepository { - private static final byte CMD_TABLE_LENGTH = 20; - private static final byte REF_TABLE_SIZE = 5; - private static final short HEAP_SIZE = 0x1000; - private static final byte INT_TABLE_SIZE = 10; - private static final byte TYPE_ARRAY_SIZE = 100; - private static final byte INT_SIZE = 4; - private static final byte LONG_SIZE = 8; - private static final short ENTROPY_POOL_SIZE = 32; - - private KMCommand[] commandTable = null; - private KMContext context = null; - private byte[] buffer = null; - private AESKey masterKey = null; - private boolean contextLocked = false; - private KMEncoder encoder = null; - private KMDecoder decoder = null; - - private KMByteBlob[] byteBlobRefTable = null; - private byte blobRefIndex = 0; - private KMInteger[] integerRefTable = null; - private byte intRefIndex = 0; - private KMArray[] arrayRefTable = null; - private byte arrayRefIndex = 0; - private KMVector[] vectorRefTable = null; - private byte vectorRefIndex = 0; - private KMEnum[] enumRefTable = null; - private byte enumRefIndex = 0; - private KMByteTag[] byteTagRefTable = null; - private byte byteTagRefIndex = 0; - private KMIntegerTag[] intTagRefTable = null; - private byte intTagRefIndex = 0; - private KMIntegerArrayTag[] intArrayTagRefTable = null; - private byte intArrayTagRefIndex = 0; - private KMEnumTag[] enumTagRefTable = null; - private byte enumTagRefIndex = 0; - private KMEnumArrayTag[] enumArrayTagRefTable = null; - private byte enumArrayTagRefIndex = 0; - private KMBoolTag[] boolTagRefTable = null; - private byte boolTagRefIndex = 0; - private KMKeyParameters[] keyParametersRefTable = null; - private byte keyParametersRefIndex = 0; - private KMKeyCharacteristics[] keyCharRefTable = null; - private byte keyCharRefIndex = 0; - private KMVerificationToken[] verTokenRefTable = null; - private byte verTokenRefIndex = 0; - private KMHmacSharingParameters[] hmacSharingParamsRefTable = null; - private byte hmacSharingParamsRefIndex = 0; - private KMHardwareAuthToken[] hwAuthTokenRefTable = null; - private byte hwAuthTokenRefIndex = 0; - private KMOperationState[] opStateRefTable = null; - private byte opStateRefIndex = 0; - private KMType[] typeRefTable = null; - private byte typeRefIndex = 0; - - private byte[] byteHeap = null; - private short byteHeapIndex = 0; - private Object[] uint32Array = null; - private byte uint32Index = 0; - private Object[] uint64Array = null; - private byte uint64Index = 0; - private KMOperationState[] operationStateTable = null; - private byte[] entropyPool = null; - private byte[] counter; - - public void initialize() { - // Initialize buffers and context. - JCSystem.beginTransaction(); - encoder = new KMEncoder(); - decoder = new KMDecoder(); - buffer = new byte[KMKeymasterApplet.MAX_LENGTH]; - context = new KMContext(); - context.setRepository(this); - contextLocked = false; - operationStateTable = new KMOperationState[4]; - // Initialize command table. - commandTable = new KMCommand[CMD_TABLE_LENGTH]; - commandTable[0] = new KMProvisionCmd(); - commandTable[1] = new KMGenerateKeyCmd(); - commandTable[2] = new KMImportKeyCmd(); - commandTable[3] = new KMExportKeyCmd(); - commandTable[4] = new KMComputeSharedHmacCmd(); - commandTable[5] = new KMBeginOperationCmd(); - commandTable[6] = new KMUpdateOperationCmd(); - commandTable[7] = new KMFinishOperationCmd(); - commandTable[8] = new KMAbortOperationCmd(); - commandTable[9] = new KMVerifyAuthorizationCmd(); - commandTable[10] = new KMAddRngEntropyCmd(); - commandTable[11] = new KMImportWrappedKeyCmd(); - commandTable[12] = new KMAttestKeyCmd(); - commandTable[13] = new KMUpgradeKeyCmd(); - commandTable[14] = new KMDeleteKeyCmd(); - commandTable[15] = new KMDeleteAllKeysCmd(); - commandTable[16] = new KMDestroyAttestationIdsCmd(); - commandTable[17] = new KMGetHWInfoCmd(); - commandTable[18] = new KMGetKeyCharacteristicsCmd(); - commandTable[19] = new KMGetHmacSharingParametersCmd(); - // Initialize masterkey - AES 256 bit key. - if (masterKey == null) { - masterKey = - (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); - } - // Initialize types - KMType.initialize(this); - byteBlobRefTable = new KMByteBlob[(short)(REF_TABLE_SIZE*4)]; - KMByteBlob.create(byteBlobRefTable); - integerRefTable = new KMInteger[REF_TABLE_SIZE]; - KMInteger.create(integerRefTable); - arrayRefTable = new KMArray[(short)(REF_TABLE_SIZE*4)]; - KMArray.create(arrayRefTable); - vectorRefTable = new KMVector[REF_TABLE_SIZE]; - KMVector.create(vectorRefTable); - enumRefTable = new KMEnum[(short)(REF_TABLE_SIZE*2)]; - KMEnum.create(enumRefTable); - byteTagRefTable = new KMByteTag[REF_TABLE_SIZE]; - KMByteTag.create(byteTagRefTable); - intTagRefTable = new KMIntegerTag[REF_TABLE_SIZE]; - KMIntegerTag.create(intTagRefTable); - intArrayTagRefTable = new KMIntegerArrayTag[REF_TABLE_SIZE]; - KMIntegerArrayTag.create(intArrayTagRefTable); - enumTagRefTable = new KMEnumTag[REF_TABLE_SIZE]; - KMEnumTag.create(enumTagRefTable); - enumArrayTagRefTable = new KMEnumArrayTag[REF_TABLE_SIZE]; - KMEnumArrayTag.create(enumArrayTagRefTable); - boolTagRefTable = new KMBoolTag[REF_TABLE_SIZE]; - KMBoolTag.create(boolTagRefTable); - keyParametersRefTable = new KMKeyParameters[REF_TABLE_SIZE]; - KMKeyParameters.create(keyParametersRefTable); - keyCharRefTable = new KMKeyCharacteristics[REF_TABLE_SIZE]; - KMKeyCharacteristics.create(keyCharRefTable); - verTokenRefTable = new KMVerificationToken[REF_TABLE_SIZE]; - KMVerificationToken.create(verTokenRefTable); - hmacSharingParamsRefTable = new KMHmacSharingParameters[REF_TABLE_SIZE]; - KMHmacSharingParameters.create(hmacSharingParamsRefTable); - hwAuthTokenRefTable = new KMHardwareAuthToken[REF_TABLE_SIZE]; - KMHardwareAuthToken.create(hwAuthTokenRefTable); - opStateRefTable = new KMOperationState[REF_TABLE_SIZE]; - KMOperationState.create(opStateRefTable); - - byteHeap = new byte[HEAP_SIZE]; - uint32Array = new Object[INT_TABLE_SIZE]; - uint64Array = new Object[INT_TABLE_SIZE]; - typeRefTable = new KMType[TYPE_ARRAY_SIZE]; - + //TODO make the sizes configurable + public static final short HEAP_SIZE = 0x2000; + public static final short MAX_BLOB_STORAGE = 32; + public static final short AES_GCM_AUTH_TAG_LENGTH = 12; + public static final short HMAC_SEED_NONCE_SIZE = 16; + public static final short MAX_OPS = 4; + public static final short COMPUTED_HMAC_KEY_SIZE = 32; + // Boot params constants + public static final byte BOOT_KEY_MAX_SIZE = 32; + public static final byte BOOT_HASH_MAX_SIZE = 32; + // Repository attributes + private static KMRepository repository; + private byte[] masterKey; + private byte[] hmacSeed; + private byte[] hmacKey; + private byte[] computedHmacKey; + private byte[] hmacNonce; + private byte[] heap; + private Object[] operationStateTable; + private short heapIndex; + // boot parameters + public Object[] authTagRepo; + public short keyBlobCount; + public byte[] osVersion; + public byte[] osPatch; + public byte[] verifiedBootKey; + public short actualBootKeyLength; + public byte[] verifiedBootHash; + public short actualBootHashLength; + public boolean verifiedBootFlag; + public boolean selfSignedBootFlag; + public boolean deviceLockedFlag; + + public static KMRepository instance() { + return repository; + } + + public KMRepository() { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + authTagRepo = new Object[MAX_BLOB_STORAGE]; short index = 0; - while (index < INT_TABLE_SIZE) { - uint32Array[index] = new byte[INT_SIZE]; - uint64Array[index] = new byte[LONG_SIZE]; + while (index < MAX_BLOB_STORAGE) { + authTagRepo[index] = new KMAuthTag(); + ((KMAuthTag) authTagRepo[index]).reserved = false; + ((KMAuthTag) authTagRepo[index]).authTag = new byte[AES_GCM_AUTH_TAG_LENGTH]; + ((KMAuthTag) authTagRepo[index]).usageCount = 0; index++; } - entropyPool = new byte[ENTROPY_POOL_SIZE]; - counter = new byte[8]; - - JCSystem.commitTransaction(); - } - - public KMEncoder getEncoder() { - return encoder; - } - - public KMDecoder getDecoder() { - return decoder; + osVersion = new byte[4]; + osPatch = new byte[4]; + verifiedBootKey = new byte[BOOT_KEY_MAX_SIZE]; + verifiedBootHash = new byte[BOOT_HASH_MAX_SIZE]; + operationStateTable = new Object[MAX_OPS]; + index = 0; + while(index < MAX_OPS){ + operationStateTable[index] = new KMOperationState(); + ((KMOperationState)operationStateTable[index]).reset(); + index++; + } + repository = this; } - public KMCommand getCommand(byte ins) throws KMException { - short cmdIndex = 0; - while (cmdIndex < CMD_TABLE_LENGTH) { - if (commandTable[cmdIndex].getIns() == ins) { - return commandTable[cmdIndex]; + public KMOperationState reserveOperation(){ + short index = 0; + while(index < MAX_OPS){ + if(!((KMOperationState)operationStateTable[index]).isActive()){ + ((KMOperationState)operationStateTable[index]).activate(); + return (KMOperationState)operationStateTable[index]; } - cmdIndex++; + index++; } - throw new KMException(ISO7816.SW_INS_NOT_SUPPORTED); + return null; } - - public KMContext getContext() throws KMException { - if (!contextLocked) { - contextLocked = true; - return context; - } else { - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void releaseOperation(KMOperationState op){ + op.reset(); + } + public void initMasterKey(byte[] key, short len) { + if (masterKey == null) { + masterKey = new byte[len]; + Util.arrayCopy(key, (short) 0, masterKey, (short) 0, len); } } - public void onUninstall() { - masterKey = null; + public void initHmacKey(byte[] key, short len) { + if (hmacKey == null) { + hmacKey = new byte[len]; + Util.arrayCopy(key, (short) 0, hmacKey, (short) 0, len); + } } - public void onProcess() { - reset(); + public void initHmacSeed(byte[] seed, short len) { + if (hmacSeed == null) { + hmacSeed = new byte[len]; + Util.arrayCopy(seed, (short) 0, hmacSeed, (short) 0, len); + } } - private void reset() { - contextLocked = false; - Util.arrayFillNonAtomic(buffer, (short) 0, (short) buffer.length, (byte) 0); - Util.arrayFillNonAtomic(byteHeap, (short) 0, (short) buffer.length, (byte) 0); - byteHeapIndex = 0; - Util.arrayFillNonAtomic(buffer, (short) 0, (short) buffer.length, (byte) 0); - short index = 0; - while (index < typeRefTable.length) { - typeRefTable[index] = null; - index++; - } - typeRefIndex = 0; - index = 0; - while (index < uint32Array.length) { - byte[] num = (byte[]) uint32Array[index]; - byte numIndex = 0; - while (numIndex < INT_SIZE) { - num[numIndex] = 0; - numIndex++; - } - index++; + public void initComputedHmac(byte[] key, short start, short len) { + if (computedHmacKey == null) { + computedHmacKey = new byte[len]; + Util.arrayCopy(key, (short) 0, computedHmacKey, start, len); } - uint32Index = 0; - index = 0; - while (index < uint64Array.length) { - byte[] num = (byte[]) uint64Array[index]; - byte numIndex = 0; - while (numIndex < LONG_SIZE) { - num[numIndex] = 0; - numIndex++; - } - index++; - } - uint64Index = 0; - resetTypeObjects(byteBlobRefTable); - resetTypeObjects(integerRefTable); - resetTypeObjects(enumRefTable); - resetTypeObjects(byteTagRefTable); - resetTypeObjects(boolTagRefTable); - resetTypeObjects(arrayRefTable); - resetTypeObjects(enumTagRefTable); - resetTypeObjects(enumArrayTagRefTable); - resetTypeObjects(intTagRefTable); - resetTypeObjects(intArrayTagRefTable); - resetTypeObjects(vectorRefTable); - resetTypeObjects(keyCharRefTable); - resetTypeObjects(keyParametersRefTable); - resetTypeObjects(hmacSharingParamsRefTable); - resetTypeObjects(hwAuthTokenRefTable); - resetTypeObjects(verTokenRefTable); } - public void resetTypeObjects(KMType[] type){ - byte index = 0; - while(index < type.length){ - type[index].init(); - index++; + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (hmacNonce == null) { + hmacNonce = new byte[len]; + } else if (len != hmacNonce.length) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } - } - public void onDeselect() { - // TODO clear operation state? + Util.arrayCopy(nonce, (short) 0, hmacSeed, (short) 0, len); } - public void onSelect() { - // Nothing to be done currently. + public void onUninstall() { + // TODO change this + Util.arrayFillNonAtomic(masterKey, (short) 0, (short) masterKey.length, (byte) 0); } - public byte[] getBuffer() { - return buffer; - } + public void onProcess() {} - public AESKey getMasterKey() { - return masterKey; + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, heapIndex, (byte) 0); + heapIndex = 0; } - // Allocate 4 bytes or 8 bytes buffer - public byte[] newIntegerArray(short length) { - if (length == 4) { - if (uint32Index >= uint32Array.length) { - // TODO this is placeholder exception value. This needs to be replaced by 910E, 91A1 or 9210 - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - byte[] ret = (byte[]) uint32Array[uint32Index]; - uint32Index++; - return ret; - } else if (length == 8) { - if (uint64Index >= uint64Array.length) { - // TODO this is placeholder exception value. This needs to be replaced by 910E, 91A1 or 9210 - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - byte[] ret = (byte[]) uint64Array[uint64Index]; - uint64Index++; - return ret; - } else { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - } + public void onDeselect() {} - public KMByteBlob newByteBlob() { - if (blobRefIndex >= byteBlobRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMByteBlob ret = byteBlobRefTable[blobRefIndex]; - blobRefIndex++; - return ret; - } + public void onSelect() {} - public KMInteger newInteger() { - if (intRefIndex >= integerRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMInteger ret = integerRefTable[intRefIndex]; - intRefIndex++; - return ret; + public byte[] getMasterKeySecret() { + return masterKey; } - public KMEnumTag newEnumTag() { - if (enumTagRefIndex >= enumTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public short alloc(short length) { + if (((short) (heapIndex + length)) > heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - KMEnumTag ret = enumTagRefTable[enumTagRefIndex]; - enumTagRefIndex++; - return ret; + heapIndex += length; + return (short) (heapIndex - length); } - public KMEnumArrayTag newEnumArrayTag() { - if (enumArrayTagRefIndex >= enumArrayTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMEnumArrayTag ret = enumArrayTagRefTable[enumArrayTagRefIndex]; - enumArrayTagRefIndex++; - return ret; + public byte[] getHeap() { + return heap; } - public KMIntegerTag newIntegerTag() { - if (intTagRefIndex >= intTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMIntegerTag ret = intTagRefTable[intTagRefIndex]; - intTagRefIndex++; - return ret; + public byte[] getHmacSeed() { + return hmacSeed; } - public KMIntegerArrayTag newIntegerArrayTag() { - if (intArrayTagRefIndex >= intArrayTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMIntegerArrayTag ret = intArrayTagRefTable[intArrayTagRefIndex]; - intArrayTagRefIndex++; - return ret; + public byte[] getHmacKey() { + return hmacKey; } - public KMBoolTag newBoolTag() { - if (boolTagRefIndex >= boolTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMBoolTag ret = boolTagRefTable[boolTagRefIndex]; - boolTagRefIndex++; - return ret; + public byte[] getHmacNonce() { + return hmacNonce; } - public KMByteTag newByteTag() { - if (byteTagRefIndex >= byteTagRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMByteTag ret = byteTagRefTable[byteTagRefIndex]; - byteTagRefIndex++; - return ret; + public void setHmacNonce(byte[] hmacNonce) { + Util.arrayCopy(hmacNonce, (short) 0, this.hmacNonce, (short) 0, HMAC_SEED_NONCE_SIZE); } - - public KMKeyParameters newKeyParameters() { - if (keyParametersRefIndex >= keyParametersRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMKeyParameters ret = keyParametersRefTable[keyParametersRefIndex]; - keyParametersRefIndex++; - return ret; + public byte[] getComputedHmacKey() { + return computedHmacKey; } - public KMArray newArray() { - if (arrayRefIndex >= arrayRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMArray ret = arrayRefTable[arrayRefIndex]; - arrayRefIndex++; - return ret; + public void setComputedHmacKey(byte[] computedHmacKey) { + Util.arrayCopy( computedHmacKey, (short) 0, this.computedHmacKey, (short) 0, COMPUTED_HMAC_KEY_SIZE); } - public KMKeyCharacteristics newKeyCharacteristics() { - if (keyCharRefIndex >= keyCharRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void persistAuthTag(short authTag) { + short index = 0; + while (index < MAX_BLOB_STORAGE) { + if (!((KMAuthTag) authTagRepo[index]).reserved) { + JCSystem.beginTransaction(); + ((KMAuthTag) authTagRepo[index]).reserved = true; + Util.arrayCopy( + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + ((KMAuthTag) authTagRepo[index]).authTag , + (short) 0, + AES_GCM_AUTH_TAG_LENGTH); + keyBlobCount++; + JCSystem.commitTransaction(); + break; + } + index++; } - KMKeyCharacteristics ret = keyCharRefTable[keyCharRefIndex]; - keyCharRefIndex++; - return ret; } - public KMHardwareAuthToken newHwAuthToken() { - if (hwAuthTokenRefIndex >= hwAuthTokenRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public boolean validateAuthTag(short authTag) { + KMAuthTag tag = findTag(authTag); + if(tag != null){ + return true; } - KMHardwareAuthToken ret = hwAuthTokenRefTable[hwAuthTokenRefIndex]; - hwAuthTokenRefIndex++; - return ret; + return false; } - public KMHmacSharingParameters newHmacSharingParameters() { - if (hmacSharingParamsRefIndex >= hmacSharingParamsRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void removeAuthTag(short authTag) { + KMAuthTag tag = findTag(authTag); + if(tag == null){ + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } - KMHmacSharingParameters ret = hmacSharingParamsRefTable[hmacSharingParamsRefIndex]; - hmacSharingParamsRefIndex++; - return ret; - } - - public KMVerificationToken newVerificationToken() { - if (verTokenRefIndex >= verTokenRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + JCSystem.beginTransaction(); + tag.reserved = false; + short index = 0; + while(index < AES_GCM_AUTH_TAG_LENGTH){ + tag.authTag[index] = 0; + index++; } - KMVerificationToken ret = verTokenRefTable[verTokenRefIndex]; - verTokenRefIndex++; - return ret; + tag.usageCount = 0; + keyBlobCount--; + JCSystem.commitTransaction(); } - public KMOperationState newOperationState() { - if (opStateRefIndex >= opStateRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void removeAllAuthTags() { + JCSystem.beginTransaction(); + KMAuthTag tag = null; + short index = 0; + short i = 0; + while (index < MAX_BLOB_STORAGE) { + tag = (KMAuthTag) authTagRepo[index]; + tag.reserved = false; + i = 0; + while(i < AES_GCM_AUTH_TAG_LENGTH){ + tag.authTag[i] = 0; + i++; + } + tag.usageCount = 0; + index++; } - KMOperationState ret = operationStateTable[opStateRefIndex]; - opStateRefIndex++; - return ret; + keyBlobCount = 0; + JCSystem.commitTransaction(); } - public void releaseOperationState(KMOperationState state){ - opStateRefIndex--; - if(opStateRefIndex <0){ - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - opStateRefTable[opStateRefIndex] = state; - } - public KMVector newVector() { - if (vectorRefIndex >= vectorRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public KMAuthTag findTag(short authTag) { + short index = 0; + short found = 0; + while (index < MAX_BLOB_STORAGE) { + if (((KMAuthTag) authTagRepo[index]).reserved) { + found = + Util.arrayCompare( + ((KMAuthTag) authTagRepo[index]).authTag, + (short) 0, + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + AES_GCM_AUTH_TAG_LENGTH); + if (found == 0) { + return (KMAuthTag) authTagRepo[index]; + } + } + index++; } - KMVector ret = vectorRefTable[vectorRefIndex]; - vectorRefIndex++; - return ret; + return null; } - public KMEnum newEnum() { - if (enumRefIndex >= enumRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public short getRateLimitedKeyCount(short authTag) { + KMAuthTag tag = findTag(authTag); + if (tag != null) { + return tag.usageCount; } - KMEnum ret = enumRefTable[enumRefIndex]; - enumRefIndex++; - return ret; + return KMType.INVALID_VALUE; } - public KMType[] getTypeArrayRef(){ - return typeRefTable; - } - - public byte[] getByteHeapRef(){ - return byteHeap; - } - - public short newTypeArray(short length) { - if (((short) (typeRefIndex + length)) >= typeRefTable.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void setRateLimitedKeyCount(short authTag, short val) { + KMAuthTag tag = findTag(authTag); + JCSystem.beginTransaction(); + if (tag != null) { + tag.usageCount = val; } - typeRefIndex += length; - return (short) (typeRefIndex - length); + JCSystem.commitTransaction(); } - public short newByteArray(short length) { - if (((short) (byteHeapIndex + length)) >= byteHeap.length) { - // TODO this is placeholder exception value. - throw new KMException(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public KMOperationState findOperation(short opHandle) { + short index = 0; + while(index < MAX_OPS){ + if(((KMOperationState)operationStateTable[index]).isActive() && + ((KMOperationState)operationStateTable[index]).handle() == opHandle){ + return (KMOperationState)operationStateTable[index]; + } + index++; } - byteHeapIndex += length; - return (short) (byteHeapIndex - length); - } - - public byte[] getEntropyPool() { - return entropyPool; + return null; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java b/Applet/Applet/src/com/android/javacard/keymaster/KMRng.java similarity index 62% rename from Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java rename to Applet/Applet/src/com/android/javacard/keymaster/KMRng.java index 5b42ae9d..a2e79901 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMRng.java @@ -1,6 +1,8 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.AESKey; import javacard.security.CryptoException; @@ -8,28 +10,23 @@ import javacard.security.RandomData; import javacardx.crypto.Cipher; -public class KMUtil { - public static final byte AES_BLOCK_SIZE = 16; - private static KMRepository repository = null; +public class KMRng { + public static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys + public static final byte[] aesICV = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; private static byte[] counter; - private static boolean initialized = false; private static AESKey aesKey; - private static Cipher aesEcb; - - public static void init(KMRepository repo) { - if (!initialized) { - KMUtil.repository = repo; - counter = repo.newIntegerArray((short) 8); - KMUtil.initEntropyPool(repo.getEntropyPool()); + private static Cipher aesCbc; + private static byte[] entropyPool; + public static void init() { + entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); + counter = JCSystem.makeTransientByteArray((short)8, JCSystem.CLEAR_ON_RESET); + KMRng.initEntropyPool(entropyPool); try { - aesEcb = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); + aesCbc = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); } catch (CryptoException exp) { - // TODO change this to proper error code - throw new KMException(ISO7816.SW_UNKNOWN); + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } - aesKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); - initialized = true; - } + aesKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); } public static void initEntropyPool(byte[] pool) { @@ -40,26 +37,31 @@ public static void initEntropyPool(byte[] pool) { } try { trng = RandomData.getInstance(RandomData.ALG_TRNG); + trng.nextBytes(pool, (short) 0, (short) pool.length); } catch (CryptoException exp) { if (exp.getReason() == CryptoException.NO_SUCH_ALGORITHM) { //TODO change this when possible // simulator does not support TRNG algorithm. So, PRNG algorithm (deprecated) is used. trng = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); + trng.nextBytes(pool, (short) 0, (short) pool.length); } else { // TODO change this to proper error code - throw new KMException(ISO7816.SW_UNKNOWN); + ISOException.throwIt(ISO7816.SW_UNKNOWN); } } - trng.nextBytes(pool, (short) 0, (short) pool.length); + } // Generate a secure random number from existing entropy pool. This uses aes ecb algorithm with // 8 byte counter and 16 byte block size. public static void newRandomNumber(byte[] num, short startOff, short length) { - byte[] bufPtr = repository.getByteHeapRef(); - short countBufInd = repository.newByteArray(AES_BLOCK_SIZE); - short randBufInd = repository.newByteArray(AES_BLOCK_SIZE); - short len = AES_BLOCK_SIZE; + KMRepository repository = KMRepository.instance(); + byte[] bufPtr = repository.getHeap(); + short countBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short randBufInd = repository.alloc(KMKeymasterApplet.AES_BLOCK_SIZE); + short len = KMKeymasterApplet.AES_BLOCK_SIZE; + aesKey.setKey(entropyPool, (short) 0); + aesCbc.init(aesKey, Cipher.MODE_ENCRYPT, aesICV, (short)0, (short)16); while (length > 0) { if (length < len ) len = length; // increment counter by one @@ -67,9 +69,7 @@ public static void newRandomNumber(byte[] num, short startOff, short length) { // copy the 8 byte counter into the 16 byte counter buffer. Util.arrayCopy(counter, (short) 0, bufPtr, countBufInd, (short) counter.length); // encrypt the counter buffer with existing entropy which forms the aes key. - aesKey.setKey(repository.getEntropyPool(), (short) 0); - aesEcb.init(aesKey, Cipher.MODE_ENCRYPT); - aesEcb.doFinal(bufPtr, countBufInd, AES_BLOCK_SIZE, bufPtr, randBufInd); + aesCbc.doFinal(bufPtr, countBufInd, KMKeymasterApplet.AES_BLOCK_SIZE, bufPtr, randBufInd); // copy the encrypted counter block to buffer passed in the argument Util.arrayCopy(bufPtr, randBufInd, num, startOff, len); length = (short) (length - len); @@ -92,10 +92,13 @@ private static void incrementCounter() { } else { // if msb is not set then increment the counter counter[index]++; - // is the msb still not set i.e. no carry over - if (counter[index] >= 0) break; // then break - else index--; // else go to the higher order byte + break; } } } + + public static byte[] getEntropyPool() { + return entropyPool; + } + } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMTag.java index c6a1b5ae..709f56f8 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMTag.java @@ -16,8 +16,9 @@ package com.android.javacard.keymaster; -public abstract class KMTag extends KMType { - public abstract short getTagType(); +import javacard.framework.Util; - public abstract short getKey(); +public class KMTag extends KMType { + public static short getTagType(short ptr){return Util.getShort(heap, (short)(ptr+TLV_HEADER_SIZE));} + public static short getKey(short ptr){return Util.getShort(heap, (short)(ptr+TLV_HEADER_SIZE+2));} } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMType.java b/Applet/Applet/src/com/android/javacard/keymaster/KMType.java index 2aa08271..f136bd83 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMType.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMType.java @@ -16,8 +16,27 @@ package com.android.javacard.keymaster; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + public abstract class KMType { - public static final short TAG_TYPE_MASK = (short) 0xF000; + public static final short INVALID_VALUE = (short)0x8000; + protected static final byte TLV_HEADER_SIZE = 3; + + // Types + public static final byte BYTE_BLOB_TYPE = 0x01; + public static final byte INTEGER_TYPE = 0x02; + public static final byte ENUM_TYPE = 0x03; + public static final byte TAG_TYPE = 0x04; + public static final byte ARRAY_TYPE = 0x05; + public static final byte KEY_PARAM_TYPE = 0x06; + public static final byte KEY_CHAR_TYPE = 0x07; + public static final byte HW_AUTH_TOKEN_TYPE = 0x08; + public static final byte VERIFICATION_TOKEN_TYPE = 0x09; + public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; + + // Tag Types public static final short INVALID_TAG = 0x0000; public static final short ENUM_TAG = 0x1000; public static final short ENUM_ARRAY_TAG = 0x2000; @@ -29,6 +48,7 @@ public abstract class KMType { public static final short BIGNUM_TAG = (short) 0x8000; public static final short BYTES_TAG = (short) 0x9000; public static final short ULONG_ARRAY_TAG = (short) 0xA000; + public static final short TAG_TYPE_MASK = (short) 0xF000; // Enum Tag // Algorithm Enum Tag key and values @@ -56,6 +76,8 @@ public abstract class KMType { public static final byte USER_AUTH_NONE = 0x00; public static final byte PASSWORD = 0x01; public static final byte FINGERPRINT = 0x02; + public static final byte BOTH = 0x03; + // have to be power of 2 public static final byte ANY = (byte) 0xFF; // Origin Enum Tag key and values. @@ -88,21 +110,38 @@ public abstract class KMType { public static final byte PKCS8 = 0x01; public static final byte RAW = 0x03; + // Verified Boot State + public static final short VERIFIED_BOOT_STATE = (short) 0xF003; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte VERIFIED_BOOT = 0x02; + + // Verified Boot Key + public static final short VERIFIED_BOOT_KEY = (short) 0xF004; + + // Verified Boot Hash + public static final short VERIFIED_BOOT_HASH = (short) 0xF005; + + // Device Locked + public static final short DEVICE_LOCKED = (short) 0xF006; + public static final byte DEVICE_LOCKED_TRUE = 0x01; + public static final byte DEVICE_LOCKED_FALSE = 0x00; + // Enum Array Tag // Purpose - public static final short PURPOSE = 0x0002; - public static final byte ENCRYPT = 0x01; - public static final byte DECRYPT = 0x02; - public static final byte SIGN = 0x04; - public static final byte VERIFY = 0x05; - public static final byte WRAP_KEY = 0x06; - public static final byte ATTEST_KEY = (byte) 0x7F; + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte WRAP_KEY = 0x05; + public static final byte ATTEST_KEY = (byte) 0x7F; /* TODO This is not present in types.hal */ // Block mode public static final short BLOCK_MODE = 0x0004; public static final byte ECB = 0x01; public static final byte CBC = 0x02; public static final byte CTR = 0x04; + public static final byte GCM = 0x20; // Digest public static final short DIGEST = 0x0005; @@ -156,7 +195,7 @@ public abstract class KMType { public static final short ACTIVE_DATETIME = 0x0190; public static final short ORIGINATION_EXPIRE_DATETIME = 0x0191; public static final short USAGE_EXPIRE_DATETIME = 0x0192; - public static final short CREATION_DATETIME = 0x0193; + public static final short CREATION_DATETIME = 0x02BD;//0x0193; // Integer Array Tags - ULONG_REP and UINT_REP. // User Secure Id @@ -225,12 +264,29 @@ public abstract class KMType { public static final byte NO_VALUE = (byte) 0xff; protected static KMRepository repository; + protected static byte[] heap; - public static void initialize(KMRepository repo) { - KMType.repository = repo; + public static void initialize() { + KMType.repository = KMRepository.instance(); + KMType.heap = repository.getHeap(); } - public abstract void init(); + public static byte getType(short ptr){return heap[ptr];} + public static short length(short ptr){return Util.getShort(heap, (short)(ptr+1));} + public static short getValue(short ptr){return Util.getShort(heap, (short)(ptr+TLV_HEADER_SIZE));} - public abstract short length(); + protected static short instance(byte type, short length){ + if (length < 0) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + short ptr = repository.alloc((short) (length + TLV_HEADER_SIZE)); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + protected static short exp(byte type) { + short ptr = repository.alloc(TLV_HEADER_SIZE); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), INVALID_VALUE); + return ptr; + } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMUpdateOperationCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMUpdateOperationCmd.java deleted file mode 100644 index 5b1e58f6..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMUpdateOperationCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMUpdateOperationCmd extends KMAbstractCmd { - public static final byte INS_UPDATE_OPERATION_CMD = 0x20; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_UPDATE_OPERATION_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMUpgradeKeyCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMUpgradeKeyCmd.java deleted file mode 100644 index d883068f..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMUpgradeKeyCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMUpgradeKeyCmd extends KMAbstractCmd { - public static final byte INS_UPGRADE_KEY_CMD = 0x15; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_UPGRADE_KEY_CMD; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMVector.java b/Applet/Applet/src/com/android/javacard/keymaster/KMVector.java deleted file mode 100644 index fb9d20f5..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMVector.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMVector extends KMType { - private KMType type; - private KMArray vals; - - private KMVector() { - init(); - } - - @Override - public void init() { - vals = null; - type = null; - } - - @Override - public short length() { - return vals.length(); - } - - public static KMVector instance(KMType type) { - KMVector inst = repository.newVector(); - inst.type = type; - inst.vals = KMArray.instance(); - return inst; - } - - public static KMVector instance(KMType type, short length) { - KMVector inst = repository.newVector(); - inst.type = type; - inst.vals = KMArray.instance(length); - return inst; - } - - public static void create(KMVector[] vectorRefTable) { - byte index = 0; - while (index < vectorRefTable.length) { - vectorRefTable[index] = new KMVector(); - index++; - } - } - - public KMArray getVals() { - return vals; - } - - public KMVector withLength(short length) { - this.vals.withLength(length); - return this; - } - - public KMVector add(short index, KMType val) { - vals.add(index, val); - return this; - } - - public KMType get(short index) { - return vals.get(index); - } - - public void setVals(KMArray vals) { - this.vals = vals; - } - - public KMType getType() { - return type; - } -} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java index f52fae55..c1c6d23c 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -17,6 +17,8 @@ package com.android.javacard.keymaster; import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; public class KMVerificationToken extends KMType { public static final byte CHALLENGE = 0x00; @@ -24,71 +26,121 @@ public class KMVerificationToken extends KMType { public static final byte PARAMETERS_VERIFIED = 0x02; public static final byte SECURITY_LEVEL = 0x03; public static final byte MAC = 0x04; - private KMArray vals; - private KMVerificationToken() { - init(); + private static KMVerificationToken prototype; + private static short instPtr; + + private KMVerificationToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short)5); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + //arr.add(PARAMETERS_VERIFIED, KMKeyParameters.exp()); + arr.add(PARAMETERS_VERIFIED, KMByteBlob.exp()); + arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE)); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMVerificationToken proto(short ptr) { + if (prototype == null) prototype = new KMVerificationToken(); + instPtr = ptr; + return prototype; + } + + + public static short instance() { + short arrPtr = KMArray.instance((short)5); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short)0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short)0)); + //arr.add(PARAMETERS_VERIFIED, KMKeyParameters.exp()); + arr.add(PARAMETERS_VERIFIED, KMByteBlob.instance((short)0)); + arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + arr.add(MAC, KMByteBlob.instance((short)0)); + return instance(arrPtr); } - @Override - public void init() { - vals = null; + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if(arr.length() != 5)ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short)2); + Util.setShort(heap, (short)(ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMVerificationToken cast(short ptr) { + if (heap[ptr] != VERIFICATION_TOKEN_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if(heap[arrPtr] != ARRAY_TYPE) ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + return proto(ptr); + } + + public short getVals() { + return Util.getShort(heap, (short) (instPtr + TLV_HEADER_SIZE)); } - @Override public short length() { - return vals.length(); + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); } - public static void create(KMVerificationToken[] verTokenRefTable) { - byte index = 0; - while (index < verTokenRefTable.length) { - verTokenRefTable[index] = new KMVerificationToken(); - index++; - } + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); } - public static KMVerificationToken instance() { - KMVerificationToken inst = repository.newVerificationToken(); - inst.vals = KMArray.instance((short) 5); - inst.vals.add(CHALLENGE, KMInteger.instance()); - inst.vals.add(TIMESTAMP, KMInteger.instance()); - inst.vals.add(PARAMETERS_VERIFIED, KMKeyParameters.instance()); - inst.vals.add(SECURITY_LEVEL, KMEnumTag.instance(KMType.HARDWARE_TYPE)); - inst.vals.add(MAC, KMByteBlob.instance()); - return inst; + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); } - public static KMVerificationToken instance(KMArray vals) { - if (vals.length() != 5) { - throw new KMException(ISO7816.SW_WRONG_LENGTH); - } - KMVerificationToken inst = repository.newVerificationToken(); - inst.vals = vals; - return inst; + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); } - public KMInteger getChallenge() { - return (KMInteger) vals.get(CHALLENGE); + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); } - public KMInteger getTimestamp() { - return (KMInteger) vals.get(TIMESTAMP); + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); } - public KMKeyParameters getParametersVerified() { - return (KMKeyParameters) vals.get(PARAMETERS_VERIFIED); + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); } - public byte getSecurityLevel() { - return ((KMEnumTag) vals.get(SECURITY_LEVEL)).getValue(); + public short getParametersVerified() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(PARAMETERS_VERIFIED); } - public KMByteBlob getMac() { - return (KMByteBlob) vals.get(MAC); + public void setParametersVerified(short vals) { + // KMKeyParameters.cast(vals); + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(PARAMETERS_VERIFIED, vals); } - public KMArray getVals() { - return vals; + public short getSecurityLevel() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SECURITY_LEVEL); } + + public void setSecurityLevel(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SECURITY_LEVEL, vals); + } + } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMVerifyAuthorizationCmd.java b/Applet/Applet/src/com/android/javacard/keymaster/KMVerifyAuthorizationCmd.java deleted file mode 100644 index 33289b44..00000000 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMVerifyAuthorizationCmd.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright(C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.javacard.keymaster; - -public class KMVerifyAuthorizationCmd extends KMAbstractCmd { - public static final byte INS_VERIFY_AUTHORIZATION_CMD = 0x1B; - - @Override - protected KMArray getExpectedArgs() { - return null; - } - - @Override - protected KMArray process(KMArray args, KMContext context) { - return null; - } - - @Override - public byte getIns() { - return INS_VERIFY_AUTHORIZATION_CMD; - } -} diff --git a/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java b/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java index 15e61109..52bcf435 100644 --- a/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java +++ b/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java @@ -17,11 +17,19 @@ package com.android.javacard.test; import com.android.javacard.keymaster.KMArray; +import com.android.javacard.keymaster.KMBoolTag; import com.android.javacard.keymaster.KMByteBlob; +import com.android.javacard.keymaster.KMByteTag; +import com.android.javacard.keymaster.KMCryptoProvider; +import com.android.javacard.keymaster.KMCryptoProviderImpl; import com.android.javacard.keymaster.KMDecoder; import com.android.javacard.keymaster.KMEncoder; import com.android.javacard.keymaster.KMEnum; +import com.android.javacard.keymaster.KMEnumArrayTag; import com.android.javacard.keymaster.KMEnumTag; +import com.android.javacard.keymaster.KMInteger; +import com.android.javacard.keymaster.KMIntegerTag; +import com.android.javacard.keymaster.KMKeyCharacteristics; import com.android.javacard.keymaster.KMKeyParameters; import com.android.javacard.keymaster.KMKeymasterApplet; import com.android.javacard.keymaster.KMType; @@ -29,18 +37,32 @@ import com.licel.jcardsim.utils.AIDUtil; import javacard.framework.AID; import javacard.framework.Util; -import javacard.security.RandomData; +import javacard.security.AESKey; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.RSAPrivateKey; import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; import org.junit.Assert; import org.junit.Test; -import org.junit.experimental.theories.suppliers.TestedOn; public class KMFrameworkTest { + private short status; + private short keyCharacteristics; + private short keyBlob; + private KMCryptoProvider sim; @Test public void test_Lifecycle_Success() { // Create simulator + //KMJcardSimulator.jcardSim = true; + sim = KMCryptoProviderImpl.instance(); + sim.bypassAesGcm(); CardSimulator simulator = new CardSimulator(); // Install applet @@ -49,43 +71,376 @@ public void test_Lifecycle_Success() { // Select applet simulator.selectApplet(appletAID1); +// testEncodeDecode(); testProvisionCmd(simulator); + testSetBootParams(simulator); testGetHwInfoCmd(simulator); testAddRngEntropyCmd(simulator); - + testGenerateRsaKey(simulator); + testImportRsaKey(simulator); + testGetKeyCharacteristics(simulator); + testGenerateAesKey(simulator); + testImportAesKey(simulator); + testGetKeyCharacteristics(simulator); + testGenerateEcKey(simulator); + testImportEcKey(simulator); + testGetKeyCharacteristics(simulator); + testGenerate3DesKey(simulator); + testImportDesKey(simulator); + testGetKeyCharacteristics(simulator); + testGenerateHmacKey(simulator); + testImportHmacKey(simulator); + testGetKeyCharacteristics(simulator); // Delete i.e. uninstall applet simulator.deleteApplet(appletAID1); } + private void testEncodeDecode() { + //128 + //ecb ode - blockmode + //padding pkcs 7 + short arrPtr = KMArray.instance((short)4); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)128)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ECB); + short blockMode = KMEnumArrayTag.instance(KMType.BLOCK_MODE,byteBlob); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.PKCS7); + short paddingMode = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + KMArray.cast(arrPtr).add((short)0, boolTag); + KMArray.cast(arrPtr).add((short)1, keySize); + KMArray.cast(arrPtr).add((short)2, blockMode); + KMArray.cast(arrPtr).add((short)3, paddingMode); + byte[] buf = new byte[1024]; + KMEncoder encode = new KMEncoder(); + KMDecoder decode = new KMDecoder(); + short len = encode.encode(arrPtr, buf, (short)0); + arrPtr = KMArray.instance((short)4); + KMArray.cast(arrPtr).add((short)0, KMBoolTag.exp()); + KMArray.cast(arrPtr).add((short)1, KMIntegerTag.exp(KMType.UINT_TAG)); + KMArray.cast(arrPtr).add((short)2, KMEnumArrayTag.exp()); + KMArray.cast(arrPtr).add((short)3, KMEnumArrayTag.exp()); + arrPtr = decode.decode(arrPtr,buf,(short)0,len); + KMArray arr = KMArray.cast(arrPtr); + short val = 0; + val = KMBoolTag.cast(arr.get((short)0)).getVal(); + val = KMInteger.cast(KMIntegerTag.cast(arr.get((short)1)).getValue()).getShort(); + val = KMEnumArrayTag.cast(arr.get((short)2)).get((short)0);; + val = KMEnumArrayTag.cast(arr.get((short)3)).get((short)0); + } + + private void testGetKeyCharacteristics(CardSimulator simulator) { + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x1D; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short keyChar = keyCharacteristics; + short len = KMKeyCharacteristics.cast(keyChar).length(); + // test provision command + short cmd = makeGetKeyCharKeyCmd(); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyChar(response); + short len2 = KMKeyCharacteristics.cast(keyCharacteristics).length(); + short hwList = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short len3 = KMKeyParameters.cast(hwList).length(); + short swList = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + short len4 = KMKeyParameters.cast(swList).length(); + Assert.assertEquals(0x9000, response.getSW()); + } + public void testProvisionCmd(CardSimulator simulator){ - byte[] buf = new byte[512]; + byte[] buf = new byte[1024]; // test provision command - KMArray cmd = makeProvisionCmd(); + short cmd = makeProvisionCmd(); KMEncoder enc = new KMEncoder(); - short actualLen = enc.encode(cmd, buf, (short) 0, (short)buf.length); + short actualLen = enc.encode(cmd, buf, (short) 0); CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x23, 0x40, 0x00, buf, 0, actualLen); //print(commandAPDU.getBytes());; ResponseAPDU response = simulator.transmitCommand(commandAPDU); Assert.assertEquals(0x9000, response.getSW()); } + public void testSetBootParams(CardSimulator simulator){ + byte[] buf = new byte[1024]; + // test provision command + short cmd = makeSetBootParamsCmd(); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 0); + CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x24, 0x40, 0x00, buf, 0, actualLen); + //print(commandAPDU.getBytes());; + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testGenerateRsaKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x10; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + // test provision command + short cmd = makeGenerateKeyCmd(KMType.RSA, (short)2048); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testImportRsaKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x11; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short cmd = makeImportKeyRsaCmd(); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testImportEcKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x11; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short cmd = makeImportKeyEcCmd(); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + private void extractKeyCharAndBlob(ResponseAPDU response) { + short ret = KMArray.instance((short) 3); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + KMArray.cast(ret).add((short) 1, KMByteBlob.exp()); + short inst = KMKeyCharacteristics.exp(); + KMArray.cast(ret).add((short) 2, inst); + KMDecoder dec = new KMDecoder(); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = dec.decode(ret, respBuf, (short) 0, len); + status = KMArray.cast(ret).get((short)0); + keyBlob = KMArray.cast(ret).get((short)1); + keyCharacteristics = KMArray.cast(ret).get((short)2); + } + + private void extractKeyChar(ResponseAPDU response) { + short ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + short inst = KMKeyCharacteristics.exp(); + KMArray.cast(ret).add((short) 1, inst); + KMDecoder dec = new KMDecoder(); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = dec.decode(ret, respBuf, (short) 0, len); + status = KMArray.cast(ret).get((short)0); + keyCharacteristics = KMArray.cast(ret).get((short)1); + } + + public void testGenerateAesKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x10; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + // test provision command + short cmd = makeGenerateKeyCmd(KMType.AES, (short)256); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testImportAesKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x11; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short cmd = makeImportKeySymmCmd(KMType.AES, (short)128); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + public void testImportHmacKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x11; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short cmd = makeImportKeySymmCmd(KMType.HMAC, (short)128); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + public void testImportDesKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x11; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short cmd = makeImportKeySymmCmd(KMType.DES, (short)168); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testGenerateHmacKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x10; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + // test provision command + short cmd = makeGenerateKeyCmdHmac(KMType.HMAC, (short)128); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testGenerate3DesKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x10; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + // test provision command + short cmd = makeGenerateKeyCmd(KMType.DES, (short)168); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } + + public void testGenerateEcKey(CardSimulator simulator){ + byte[] buf = new byte[1024]; + buf[0] = (byte)0x80; + buf[1] = (byte)0x10; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + // test provision command + short cmd = makeGenerateKeyCmd(KMType.EC, (short)256); + KMEncoder enc = new KMEncoder(); + short actualLen = enc.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, actualLen); + byte[] apdu = new byte[7+actualLen]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+actualLen)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + CommandAPDU commandAPDU = new CommandAPDU(apdu); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + extractKeyCharAndBlob(response); + Assert.assertEquals(0x9000, response.getSW()); + } public void testGetHwInfoCmd(CardSimulator simulator){ CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x1E, 0x40, 0x00); //print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(commandAPDU); KMDecoder dec = new KMDecoder(); - KMArray exp = KMArray.instance((short)3) - .add((short)0, KMEnum.instance().setType(KMType.HARDWARE_TYPE)) - .add((short)1, KMByteBlob.instance()) - .add((short)2, KMByteBlob.instance()); + short arrPtr = KMArray.instance((short)3); + KMArray exp = KMArray.cast(arrPtr); + exp.add((short)0, KMEnum.instance(KMType.HARDWARE_TYPE)); + exp.add((short)1, KMByteBlob.exp()); + exp.add((short)2, KMByteBlob.exp()); byte[] respBuf = response.getBytes(); short len = (short)respBuf.length; - KMArray resp = dec.decode(exp,respBuf, (short)0, len); - Assert.assertEquals(3, resp.length()); - KMEnum secLevel = (KMEnum)resp.get((short)0); - KMByteBlob kmName = (KMByteBlob) resp.get((short)1); - KMByteBlob authorName = (KMByteBlob) resp.get((short)2); - Assert.assertEquals(KMType.HARDWARE_TYPE, secLevel.getType()); + short respPtr = dec.decode(arrPtr,respBuf, (short)0, len); + Assert.assertEquals(3, KMArray.cast(respPtr).length()); + KMEnum secLevel = KMEnum.cast(KMArray.cast(respPtr).get((short)0)); + short kmName = KMArray.cast(respPtr).get((short)1); + short authorName = KMArray.cast(respPtr).get((short)2); + Assert.assertEquals(KMType.HARDWARE_TYPE, secLevel.getEnumType()); Assert.assertEquals(KMType.STRONGBOX, secLevel.getVal()); String kmNameStr = byteBlobToString(kmName); String authorNameStr = byteBlobToString(authorName); @@ -93,12 +448,13 @@ public void testGetHwInfoCmd(CardSimulator simulator){ Assert.assertEquals( "Google",authorNameStr); Assert.assertEquals(0x9000, response.getSW()); } + private void testAddRngEntropyCmd(CardSimulator simulator){ - byte[] buf = new byte[512]; + byte[] buf = new byte[1024]; // test provision command - KMArray cmd = makeAddRngEntropyCmd(); + short cmd = makeAddRngEntropyCmd(); KMEncoder enc = new KMEncoder(); - short actualLen = enc.encode(cmd, buf, (short) 0, (short)buf.length); + short actualLen = enc.encode(cmd, buf, (short) 0); CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x18, 0x40, 0x00, buf, 0, actualLen); //print(commandAPDU.getBytes()); @@ -107,8 +463,9 @@ private void testAddRngEntropyCmd(CardSimulator simulator){ } - private String byteBlobToString(KMByteBlob blob) { + private String byteBlobToString(short blobPtr) { StringBuilder sb = new StringBuilder(); + KMByteBlob blob = KMByteBlob.cast(blobPtr); for(short i = 0; i 5) + ret = decoder.decode(ret, respBuf, (short) 0, len); + else + ret = KMByteBlob.instance(respBuf, (short)0, len); + return ret; + } + + + @Test + public void testWithAesEcbPkcs7WithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.ECB, KMType.PKCS7,true); + cleanUp(); + } + @Test + public void testWithAesEcbNoPadWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.ECB, KMType.PADDING_NONE,true); + cleanUp(); + } + @Test + public void testWithDesEcbPkcs7WithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.ECB, KMType.PKCS7,true); + cleanUp(); + } + @Test + public void testWithDesEcbNoPadWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.ECB, KMType.PADDING_NONE,true); + cleanUp(); + } + @Test + public void testWithAesCbcPkcs7WithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CBC, KMType.PKCS7,true); + cleanUp(); + } + @Test + public void testWithAesCbcNoPadWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CBC, KMType.PADDING_NONE,true); + cleanUp(); + } + @Test + public void testWithDesCbcPkcs7WithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.CBC, KMType.PKCS7,true); + cleanUp(); + } + @Test + public void testWithDesCbcNoPadWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.CBC, KMType.PADDING_NONE,true); + cleanUp(); + } + + @Test + public void testWithAesEcbPkcs7(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.ECB, KMType.PKCS7,false); + cleanUp(); + } + @Test + public void testWithAesCbcPkcs7(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CBC, KMType.PKCS7,false); + cleanUp(); + } + @Test + public void testWithAesEcbNoPad(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.ECB, KMType.PADDING_NONE,false); + cleanUp(); + } + @Test + public void testWithAesCbcNoPad(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CBC, KMType.PADDING_NONE,false); + cleanUp(); + } + @Test + public void testWithDesCbcPkcs7(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.CBC, KMType.PKCS7,false); + cleanUp(); + } + @Test + public void testWithDesCbcNoPad(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.CBC, KMType.PADDING_NONE,false); + cleanUp(); + } + @Test + public void testWithDesEcbNoPad(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.ECB, KMType.PADDING_NONE,false); + cleanUp(); + } + @Test + public void testWithDesEcbPkcs7(){ + init(); + testEncryptDecryptWithAesDes(KMType.DES, KMType.ECB, KMType.PKCS7,false); + cleanUp(); + } + + //TODO currently cannot test OAEP SHA256 based encryption/decryption as it is not supported by + // crypto provider +/* @Test + public void testWithRsa256Oaep(){ + init(); + testEncryptDecryptWithRsa(KMType.SHA2_256, KMType.RSA_OAEP); + cleanUp(); + } +*/ + @Test + public void testWithRsaNonePkcs1(){ + init(); + testEncryptDecryptWithRsa(KMType.DIGEST_NONE, KMType.RSA_PKCS1_1_5_ENCRYPT); + cleanUp(); + } + @Test + public void testWithRsaNoneNoPad(){ + init(); + testEncryptDecryptWithRsa(KMType.DIGEST_NONE, KMType.PADDING_NONE); + cleanUp(); + } + + /* + // TODO Signing with no digest is not supported by crypto provider or javacard + @Test + public void testSignVerifyWithRsaNoneNoPad(){ + init(); + testSignVerifyWithRsa(KMType.DIGEST_NONE, KMType.RSA_PKCS1_1_5_SIGN); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaNonePkcs1(){ + init(); + testSignVerifyWithRsa(KMType.DIGEST_NONE, KMType.RSA_PKCS1_1_5_SIGN); + cleanUp(); + }*/ + + @Test + public void testSignVerifyWithHmacSHA256WithUpdate(){ + init(); + testSignVerifyWithHmac(KMType.SHA2_256, true); + cleanUp(); + } + @Test + public void testSignVerifyWithHmacSHA256(){ + init(); + testSignVerifyWithHmac(KMType.SHA2_256, false); + cleanUp(); + } + + @Test + public void testSignVerifyWithEcdsaSHA256WithUpdate(){ + init(); + testSignVerifyWithEcdsa(KMType.SHA2_256, true); + cleanUp(); + } + @Test + public void testSignVerifyWithEcdsaSHA256(){ + init(); + testSignVerifyWithEcdsa(KMType.SHA2_256, false); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaSHA256Pkcs1(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PKCS1_1_5_SIGN,false); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaSHA256Pss(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PSS,false); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaSHA256Pkcs1WithUpdate(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PKCS1_1_5_SIGN,true); + cleanUp(); + } + + @Test + public void testUpgradeKey(){ + init(); + short ret = generateHmacKey(null, null); + short keyBlobPtr = KMArray.cast(ret).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + short osVersion = KMKeyParameters.findTag(KMType.UINT_TAG,KMType.OS_VERSION,hwParams); + osVersion = KMIntegerTag.cast(osVersion).getValue(); + short osPatch = KMKeyParameters.findTag(KMType.UINT_TAG,KMType.OS_PATCH_LEVEL,hwParams); + osPatch = KMIntegerTag.cast(osPatch).getValue(); + Assert.assertEquals(KMInteger.cast(osVersion).getShort(), 1); + Assert.assertEquals(KMInteger.cast(osPatch).getShort(), 1); + setBootParams(simulator,(short) 2,(short)2); + ret = upgradeKey(KMByteBlob.instance(keyBlob, (short)0, (short)keyBlob.length),null, null); + keyBlobPtr = KMArray.cast(ret).get((short)1); + ret = getKeyCharacteristics(keyBlobPtr); + keyCharacteristics = KMArray.cast(ret).get((short)1); + hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + osVersion = KMKeyParameters.findTag(KMType.UINT_TAG,KMType.OS_VERSION,hwParams); + osVersion = KMIntegerTag.cast(osVersion).getValue(); + osPatch = KMKeyParameters.findTag(KMType.UINT_TAG,KMType.OS_PATCH_LEVEL,hwParams); + osPatch = KMIntegerTag.cast(osPatch).getValue(); + Assert.assertEquals(KMInteger.cast(osVersion).getShort(), 2); + Assert.assertEquals(KMInteger.cast(osPatch).getShort(), 2); + cleanUp(); + } + private short upgradeKey(short keyBlobPtr, byte[] clientId, byte[] appData){ + short tagCount = 0; + short clientIdTag = 0; + short appDataTag = 0; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + short keyParams = KMArray.instance(tagCount); + short tagIndex=0; + if(clientId != null)KMArray.cast(keyBlobPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(keyParams).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + keyParams = KMKeyParameters.instance(keyParams); + short arr = KMArray.instance((short)2); + KMArray.cast(arr).add((short)0,keyBlobPtr); + KMArray.cast(arr).add((short)1,keyParams); + CommandAPDU apdu = encodeApdu((byte)0x15, arr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + KMArray.cast(ret).add((short)1, KMByteBlob.exp()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + return ret; + } + @Test + public void testSignVerifyWithRsaSHA256PssWithUpdate(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PSS,true); + cleanUp(); + } + @Test + public void testAbortOperation(){ + init(); + short aesDesKeyArr = generateAesDesKey(KMType.AES, (short)128,null, null);; + short keyBlobPtr = KMArray.cast(aesDesKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + byte[] nonce = new byte[16]; + cryptoProvider.newRandomNumber(nonce,(short)0,(short)16); + short inParams = getAesDesParams(KMType.AES,KMType.ECB, KMType.PKCS7, nonce); + byte[] plainData= "Hello World 123!".getBytes(); + short ret = begin(KMType.ENCRYPT, KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), KMKeyParameters.instance(inParams), (short)0); + short opHandle = KMArray.cast(ret).get((short) 2); + opHandle = KMInteger.cast(opHandle).getShort(); + abort(KMInteger.uint_16(opHandle)); + short dataPtr = KMByteBlob.instance(plainData, (short) 0, (short) plainData.length); + ret = KMType.INVALID_VALUE; + ret = update(KMInteger.uint_16(opHandle), dataPtr, (short) 0, (short) 0, (short) 0); + Assert.assertEquals(KMError.INVALID_OPERATION_HANDLE,ret); + cleanUp(); + } + + public void testEncryptDecryptWithAesDes(byte alg, byte blockMode, byte padding, boolean update){ + short aesDesKeyArr; + if(alg == KMType.AES){ + aesDesKeyArr = generateAesDesKey(alg, (short)128,null, null); + } else{ + aesDesKeyArr = generateAesDesKey(alg, (short)168,null, null); + } + short keyBlobPtr = KMArray.cast(aesDesKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + byte[] nonce = new byte[16]; + cryptoProvider.newRandomNumber(nonce,(short)0,(short)16); + short inParams = getAesDesParams(alg,blockMode, padding, nonce); + byte[] plainData= "Hello World 123!".getBytes(); + if(update) plainData= "Hello World 123! Hip Hip Hoorah!".getBytes(); + //Encrypt + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.ENCRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,update + ); + inParams = getAesDesParams(alg,blockMode, padding, nonce); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] cipherData = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + cipherData,(short)0, (short)cipherData.length); + ret = processMessage(cipherData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.DECRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,update + ); + keyBlobPtr = KMArray.cast(ret).get((short)2); + short equal = Util.arrayCompare(plainData,(short)0,KMByteBlob.cast(keyBlobPtr).getBuffer(), + KMByteBlob.cast(keyBlobPtr).getStartOff(),(short)plainData.length); + Assert.assertTrue(equal == 0); + } + + public void testEncryptDecryptWithRsa(byte digest, byte padding){ + short rsaKeyArr = generateRsaKey(null, null); + short keyBlobPtr = KMArray.cast(rsaKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + short inParams = getRsaParams(digest, padding); + byte[] plainData = "Hello World 123!".getBytes(); + //Encrypt + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.ENCRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,false + ); + inParams = getRsaParams(digest, padding); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] cipherData = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + cipherData,(short)0, (short)cipherData.length); + ret = processMessage(cipherData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.DECRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,false + ); + keyBlobPtr = KMArray.cast(ret).get((short)2); + short equal = Util.arrayCompare(plainData,(short)0,KMByteBlob.cast(keyBlobPtr).getBuffer(), + KMByteBlob.cast(keyBlobPtr).getStartOff(),(short)plainData.length); + Assert.assertTrue(equal == 0); + } + + public void testSignVerifyWithRsa(byte digest, byte padding, boolean update){ + short rsaKeyArr = generateRsaKey(null, null); + short keyBlobPtr = KMArray.cast(rsaKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + short inParams = getRsaParams(digest, padding); + byte[] plainData = "Hello World 123!".getBytes(); + if(update) plainData= "Hello World 123! Hip Hip Hoorah!".getBytes(); + //Sign + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.SIGN, + KMKeyParameters.instance(inParams), + (short)0,null,update + ); + inParams = getRsaParams(digest, padding); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] signatureData = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + signatureData,(short)0, (short)signatureData.length); + ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.VERIFY, + KMKeyParameters.instance(inParams), + (short)0,signatureData,update + ); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + } + + public void testSignVerifyWithEcdsa(byte digest, boolean update){ + short ecKeyArr = generateEcKey(null, null); + short keyBlobPtr = KMArray.cast(ecKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + short inParams = getEcParams(digest); + byte[] plainData = "Hello World 123!".getBytes(); + if(update) plainData= "Hello World 123! Hip Hip Hoorah!".getBytes(); + //Sign + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.SIGN, + KMKeyParameters.instance(inParams), + (short)0,null,update + ); + inParams = getEcParams(digest); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] signatureData = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + signatureData,(short)0, (short)signatureData.length); + ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.VERIFY, + KMKeyParameters.instance(inParams), + (short)0,signatureData,update + ); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + } + public void testSignVerifyWithHmac(byte digest, boolean update){ + short hmacKeyArr = generateHmacKey(null, null); + short keyBlobPtr = KMArray.cast(hmacKeyArr).get((short)1); + byte[] keyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + keyBlob,(short)0, (short)keyBlob.length); + short inParams = getHmacParams(digest); + byte[] plainData = "Hello World 123!".getBytes(); + if(update) plainData= "Hello World 123! Hip Hip Hoorah!".getBytes(); + //Sign + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.SIGN, + KMKeyParameters.instance(inParams), + (short)0,null,update + ); + inParams = getHmacParams(digest); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] signatureData = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + signatureData,(short)0, (short)signatureData.length); + ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.VERIFY, + KMKeyParameters.instance(inParams), + (short)0,signatureData,update + ); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + } + + private short getAesDesParams(byte alg, byte blockMode, byte padding, byte[] nonce) { + short inParams; + if(blockMode == KMType.ECB){ + inParams = KMArray.instance((short)2); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, blockMode); + KMArray.cast(inParams).add((short)0, KMEnumArrayTag.instance(KMType.BLOCK_MODE, byteBlob)); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, padding); + KMArray.cast(inParams).add((short)1, KMEnumArrayTag.instance(KMType.PADDING, byteBlob)); + }else{ + inParams = KMArray.instance((short)3); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, blockMode); + KMArray.cast(inParams).add((short)0, KMEnumArrayTag.instance(KMType.BLOCK_MODE, byteBlob)); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, padding); + KMArray.cast(inParams).add((short)1, KMEnumArrayTag.instance(KMType.PADDING, byteBlob)); + short nonceLen = 16; + if(alg == KMType.DES) nonceLen = 8; + byteBlob = KMByteBlob.instance(nonce,(short)0, nonceLen); + KMArray.cast(inParams).add((short)2, KMByteTag.instance(KMType.NONCE, byteBlob)); + } + return inParams; + } + + private short getRsaParams(byte digest, byte padding) { + short inParams = KMArray.instance((short)2); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, digest); + KMArray.cast(inParams).add((short)0, KMEnumArrayTag.instance(KMType.DIGEST, byteBlob)); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, padding); + KMArray.cast(inParams).add((short)1, KMEnumArrayTag.instance(KMType.PADDING, byteBlob)); + return inParams; + } + + private short getEcParams(byte digest) { + short inParams = KMArray.instance((short)1); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, digest); + KMArray.cast(inParams).add((short)0, KMEnumArrayTag.instance(KMType.DIGEST, byteBlob)); + return inParams; + } + private short getHmacParams(byte digest) { + short inParams = KMArray.instance((short)2); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, digest); + KMArray.cast(inParams).add((short)0, KMEnumArrayTag.instance(KMType.DIGEST, byteBlob)); + short macLength = KMIntegerTag.instance(KMType.UINT_TAG,KMType.MAC_LENGTH, KMInteger.uint_16((short)256)); + KMArray.cast(inParams).add((short)1, macLength); + return inParams; + } + + public short processMessage( + byte[] data, + short keyBlob, + byte keyPurpose, + short inParams, + short hwToken, + byte[] signature, + boolean updateFlag) { + short beginResp = begin(keyPurpose, keyBlob, inParams, hwToken); + short opHandle = KMArray.cast(beginResp).get((short) 2); + opHandle = KMInteger.cast(opHandle).getShort(); + short dataPtr = KMByteBlob.instance(data, (short) 0, (short) data.length); + short ret = KMType.INVALID_VALUE; + byte[] outputData = new byte[128]; + short len=0; + if (updateFlag) { + dataPtr = KMByteBlob.instance(data, (short) 0, (short) 16); + ret = update(KMInteger.uint_16(opHandle), dataPtr, (short) 0, (short) 0, (short) 0); + dataPtr = KMArray.cast(ret).get((short) 3); + if (KMByteBlob.cast(dataPtr).length() > 0) { + Util.arrayCopyNonAtomic( + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + outputData, + (short) 0, + KMByteBlob.cast(dataPtr).length()); + len = KMByteBlob.cast(dataPtr).length(); + dataPtr = KMByteBlob.instance(data, len, (short) (data.length - len)); + }else{ + dataPtr = KMByteBlob.instance(data, (short)16, (short) (data.length - 16)); + } + } + + if (keyPurpose == KMType.VERIFY) { + ret = finish(KMInteger.uint_16(opHandle), dataPtr, signature, (short) 0, (short) 0, (short) 0); + } else { + ret = finish(KMInteger.uint_16(opHandle), dataPtr, null, (short) 0, (short) 0, (short) 0); + } + if(len >0){ + dataPtr = KMArray.cast(ret).get((short)2); + if(KMByteBlob.cast(dataPtr).length() >0){ + Util.arrayCopyNonAtomic( + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + outputData, + len, + KMByteBlob.cast(dataPtr).length()); + len = (short)(len + KMByteBlob.cast(dataPtr).length()); + } + KMArray.cast(ret).add((short)2, KMByteBlob.instance(outputData,(short)0,len)); + } + return ret; + } + + public short begin(byte keyPurpose, short keyBlob, short keyParmas, short hwToken) { + short arrPtr = KMArray.instance((short)4); + KMArray.cast(arrPtr).add((short)0, KMEnum.instance(KMType.PURPOSE, keyPurpose)); + KMArray.cast(arrPtr).add((short)1, keyBlob); + KMArray.cast(arrPtr).add((short)2, keyParmas); + if(hwToken == 0) { + hwToken = KMHardwareAuthToken.instance(); + } + KMArray.cast(arrPtr).add((short)3, hwToken); + CommandAPDU apdu = encodeApdu((byte)0x1F, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 3); + short outParams = KMKeyParameters.exp(); + KMArray.cast(ret).add((short)0, KMInteger.exp()); + KMArray.cast(ret).add((short)1, outParams); + KMArray.cast(ret).add((short)2, KMInteger.exp()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + return ret; + } + + public short finish(short operationHandle, short data, byte[] signature, short inParams, short hwToken, short verToken) { + if(hwToken == 0) { + hwToken = KMHardwareAuthToken.instance(); + } + if(verToken == 0){ + verToken = KMVerificationToken.instance(); + } + short signatureTag; + if(signature == null){ + signatureTag = KMByteBlob.instance((short)0); + }else{ + signatureTag = KMByteBlob.instance(signature,(short)0,(short)signature.length); + } + if(inParams == 0){ + short arr = KMArray.instance((short)0); + inParams = KMKeyParameters.instance(arr); + } + short arrPtr = KMArray.instance((short)6); + KMArray.cast(arrPtr).add((short)0, operationHandle); + KMArray.cast(arrPtr).add((short)1, inParams); + KMArray.cast(arrPtr).add((short)2, data); + KMArray.cast(arrPtr).add((short)3, signatureTag); + KMArray.cast(arrPtr).add((short)4, hwToken); + KMArray.cast(arrPtr).add((short)5, verToken); + CommandAPDU apdu = encodeApdu((byte)0x21, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 3); + short outParams = KMKeyParameters.exp(); + KMArray.cast(ret).add((short)0, KMInteger.exp()); + KMArray.cast(ret).add((short)1, outParams); + KMArray.cast(ret).add((short)2, KMByteBlob.exp()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + return ret; + } + public short update(short operationHandle, short data, short inParams, short hwToken, short verToken) { + if(hwToken == 0) { + hwToken = KMHardwareAuthToken.instance(); + } + if(verToken == 0){ + verToken = KMVerificationToken.instance(); + } + if(inParams == 0){ + short arr = KMArray.instance((short)0); + inParams = KMKeyParameters.instance(arr); + } + short arrPtr = KMArray.instance((short)5); + KMArray.cast(arrPtr).add((short)0, operationHandle); + KMArray.cast(arrPtr).add((short)1, inParams); + KMArray.cast(arrPtr).add((short)2, data); + KMArray.cast(arrPtr).add((short)3, hwToken); + KMArray.cast(arrPtr).add((short)4, verToken); + CommandAPDU apdu = encodeApdu((byte)0x20, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 4); + short outParams = KMKeyParameters.exp(); + KMArray.cast(ret).add((short)0, KMInteger.exp()); + KMArray.cast(ret).add((short)1, KMInteger.exp()); + KMArray.cast(ret).add((short)2, outParams); + KMArray.cast(ret).add((short)3, KMByteBlob.exp()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + if (len > 5) { + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short) 0)).getShort(); + Assert.assertEquals(error, KMError.OK); + }else{ + ret = respBuf[1]; + } + return ret; + } + +} diff --git a/Applet/JavaCardKeymaster.scr b/Applet/JavaCardKeymaster.scr index ce15b5c9..6380faa2 100644 --- a/Applet/JavaCardKeymaster.scr +++ b/Applet/JavaCardKeymaster.scr @@ -3,15 +3,48 @@ output on; //create applet instance 0x80 0xB8 0x00 0x00 0x0c 0x0a 0xa0 0x00 0x00 0x00 0x62 0x03 0x01 0xc 0x01 0x01 0x00 0x7F; - // Select JavaCardKeymaster //aid/A000000062/03010C0101 -0x00 0xa4 0x04 0x00 0x0a 0xa0 0x00 0x00 0x00 0x62 0x03 0x01 0xc 0x01 0x01 0x7F; + 0x00 0xa4 0x04 0x00 0x0a 0xa0 0x00 0x00 0x00 0x62 0x03 0x01 0xc 0x01 0x01 0x7F; // Send provision command - this will change in future -0x80 0x23 0x40 0x00 0x3B 0x83 0xA1 0x1A 0x10 0x00 0x00 0x02 0x01 0x00 0x58 0x30 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2A 0x2B 0x2C 0x2D 0x2E 0x2F 0x7F; + 0x80 0x23 0x40 0x00 0x3B 0x83 0xA1 0x1A 0x10 0x00 0x00 0x02 0x01 0x00 0x58 0x30 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x20 0x21 0x22 0x23 0x24 0x25 0x26 0x27 0x28 0x29 0x2A 0x2B 0x2C 0x2D 0x2E 0x2F 0x7F; + +// Send Set Boot Params command + 0x80 0x24 0x40 0x00 0x49 0x86 0x01 0x01 0x58 0x20 0x30 0x30 0x30 0x31 0x31 0x31 0x32 0x32 0x32 0x33 0x33 0x33 0x34 0x34 0x34 0x35 0x35 0x35 0x36 0x36 0x36 0x37 0x37 0x37 0x38 0x38 0x38 0x39 0x39 0x39 0x30 0x30 0x58 0x20 0x30 0x30 0x30 0x31 0x31 0x31 0x32 0x32 0x32 0x33 0x33 0x33 0x34 0x34 0x34 0x35 0x35 0x35 0x36 0x36 0x36 0x37 0x37 0x37 0x38 0x38 0x38 0x39 0x39 0x39 0x30 0x30 0x02 0x00 0x7F; // Send getHardwareInfo command -0x80 0x1E 0x40 0x00 0x00 0x7F; + 0x80 0x1E 0x40 0x00 0x00 0x7F; // Send AddRngEntropy command -0x80 0x18 0x40 0x00 0x23 0x81 0x58 0x20 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x7F; \ No newline at end of file + 0x80 0x18 0x40 0x00 0x23 0x81 0x58 0x20 0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1A 0x1B 0x1C 0x1D 0x1E 0x1F 0x7F; + +// Generate Key - RSA Key command + 0x80 0x10 0x40 0x00 0x22 0x81 0xA4 0x1A 0x10 0x00 0x00 0x02 0x01 0x1A 0x30 0x00 0x00 0x03 0x19 0x08 0x00 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x1A 0x20 0x00 0x00 0x01 0x42 0x02 0x03 0x7F; + +// Generate Key - AES Key command + 0x80 0x10 0x40 0x00 0x23 0x81 0xA4 0x1A 0x10 0x00 0x00 0x02 0x18 0x20 0x1A 0x30 0x00 0x00 0x03 0x19 0x01 0x00 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x1A 0x20 0x00 0x00 0x01 0x42 0x02 0x03 0x7F; + +// Generate Key - ECC Key command + 0x80 0x10 0x40 0x00 0x22 0x81 0xA4 0x1A 0x10 0x00 0x00 0x02 0x03 0x1A 0x30 0x00 0x00 0x03 0x19 0x01 0x00 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x1A 0x20 0x00 0x00 0x01 0x42 0x02 0x03 0x7F; + +// Generate Key - DES Key command + 0x80 0x10 0x40 0x00 0x22 0x81 0xA4 0x1A 0x10 0x00 0x00 0x02 0x18 0x21 0x1A 0x30 0x00 0x00 0x03 0x18 0xA8 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x1A 0x20 0x00 0x00 0x01 0x42 0x02 0x03 0x7F; + +// Generate Key - HMAC Key command + 0x80 0x10 0x40 0x00 0x32 0x81 0xA6 0x1A 0x10 0x00 0x00 0x02 0x18 0x80 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x30 0x00 0x00 0x08 0x18 0x80 0x1A 0x30 0x00 0x00 0x03 0x18 0x80 0x1A 0x20 0x00 0x00 0x05 0x41 0x04 0x7F; + +// Import RSA Key + 0x80 0x11 0x40 0x00 0xB4 0x83 0xA5 0x1A 0x10 0x00 0x00 0x02 0x01 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x1A 0x70 0x00 0x01 0xF7 0x01 0x03 0x58 0x85 0x82 0x58 0x40 0x80 0x9A 0x87 0x4C 0xE1 0xA0 0x71 0x44 0xAE 0x45 0xDD 0x8D 0x1C 0x05 0xB4 0xD0 0x44 0x23 0xDD 0x42 0xA7 0xC9 0x53 0x44 0xAC 0x31 0x4A 0x22 0x4A 0x02 0x65 0xA0 0xAA 0x21 0xA8 0x30 0x94 0x7D 0x13 0xA1 0xBC 0x89 0x81 0xB5 0x54 0xDE 0x75 0x82 0xB9 0x0B 0x1A 0x7A 0x81 0x0C 0x51 0xE0 0x2F 0x91 0x97 0xD4 0xE8 0x33 0x27 0x61 0x58 0x40 0x92 0x6C 0x79 0x17 0xBB 0x36 0x6F 0xB7 0x58 0x25 0x84 0x98 0xA9 0x56 0x07 0xE6 0x07 0xF6 0x26 0x92 0x15 0xF6 0x21 0x9F 0x6C 0xF0 0xB4 0xE7 0x20 0x42 0xAC 0xB6 0xD8 0x30 0x61 0x06 0xC9 0x3B 0x30 0x67 0x1E 0x8D 0x74 0x11 0x8B 0x06 0x98 0xAB 0x8D 0x6A 0x6C 0xCD 0xB7 0x2F 0xC3 0xA8 0x30 0xC7 0x68 0x03 0x4F 0x72 0xC7 0x5B 0x7F; + +// Import EC Key + 0x80 0x11 0x40 0x00 0x7D 0x83 0xA4 0x1A 0x10 0x00 0x00 0x02 0x03 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x50 0x00 0x00 0xC8 0x1A 0x00 0x01 0x00 0x01 0x03 0x58 0x54 0x83 0x58 0x18 0xA6 0x68 0xDE 0xEC 0x65 0x6C 0xFB 0xEE 0xAA 0x43 0xEF 0x97 0x9D 0x10 0x82 0xF0 0x99 0x5F 0x10 0xF3 0xEE 0x9C 0x38 0x57 0x58 0x31 0x04 0x3A 0xF8 0xF4 0xFA 0x1F 0xE4 0x4D 0x62 0xA1 0xCD 0x26 0x8E 0x1A 0x5A 0xAA 0xF5 0xA8 0x94 0xE3 0x8B 0x4C 0xCE 0x49 0xA1 0x57 0x25 0x81 0x6D 0xBE 0x5C 0x3B 0x07 0x95 0xB6 0x89 0x24 0x6E 0x9D 0x25 0x22 0xE6 0x5F 0x41 0xCC 0x59 0xCE 0x25 0x0C 0x1A 0x10 0x00 0x00 0x0A 0x01 0x7F; + +// Import AES Key + 0x80 0x11 0x40 0x00 0x3F 0x83 0xA5 0x1A 0x10 0x00 0x00 0x02 0x18 0x20 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x30 0x00 0x00 0x03 0x18 0x80 0x1A 0x20 0x00 0x00 0x05 0x41 0x04 0x03 0x52 0x81 0x50 0x95 0xE6 0x79 0x36 0x64 0xA5 0xEC 0x72 0xBF 0x01 0x4C 0x83 0x6C 0xCD 0xCF 0x51 0x7F; + +// Import Hmac Key + 0x80 0x11 0x40 0x00 0x46 0x83 0xA6 0x1A 0x10 0x00 0x00 0x02 0x18 0x80 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x30 0x00 0x00 0x03 0x18 0x80 0x1A 0x20 0x00 0x00 0x05 0x41 0x04 0x1A 0x30 0x00 0x00 0x08 0x18 0x80 0x03 0x52 0x81 0x50 0xFC 0xA6 0x8F 0x58 0x68 0x93 0xDE 0xD0 0xC0 0x74 0x1C 0x6F 0x1D 0x39 0x2E 0x4A 0x7F; + +// Import Des Key + 0x80 0x11 0x40 0x00 0x3F 0x83 0xA5 0x1A 0x10 0x00 0x00 0x02 0x18 0x21 0x1A 0x30 0x00 0x01 0xF5 0x1A 0x01 0x02 0x03 0x04 0x1A 0x90 0x00 0x02 0x59 0x44 0x54 0x65 0x73 0x74 0x1A 0x30 0x00 0x00 0x03 0x18 0xA8 0x1A 0x20 0x00 0x00 0x05 0x41 0x04 0x03 0x52 0x81 0x50 0x8B 0xD4 0xD5 0x84 0x37 0x39 0xC0 0x1B 0xDB 0xED 0x3C 0x68 0x99 0x3A 0xDC 0x3D 0x7F; + diff --git a/Applet/build.xml b/Applet/build.xml index 17b0a546..41181873 100644 --- a/Applet/build.xml +++ b/Applet/build.xml @@ -4,6 +4,8 @@ + + @@ -60,7 +62,7 @@ - + + + + + + + + @@ -130,13 +146,22 @@ + + + + + + + + + diff --git a/Applet/default.output b/Applet/default.output index 49b8367e..6d9f7ea9 100644 --- a/Applet/default.output +++ b/Applet/default.output @@ -5,5 +5,16 @@ OUTPUT ON; CLA: 80, INS: b8, P1: 00, P2: 00, Lc: 0c, 0a, a0, 00, 00, 00, 62, 03, 01, 0c, 01, 01, 00, Le: 0a, a0, 00, 00, 00, 62, 03, 01, 0c, 01, 01, SW1: 90, SW2: 00 CLA: 00, INS: a4, P1: 04, P2: 00, Lc: 0a, a0, 00, 00, 00, 62, 03, 01, 0c, 01, 01, Le: 00, SW1: 90, SW2: 00 CLA: 80, INS: 23, P1: 40, P2: 00, Lc: 3b, 83, a1, 1a, 10, 00, 00, 02, 01, 00, 58, 30, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 2a, 2b, 2c, 2d, 2e, 2f, Le: 00, SW1: 90, SW2: 00 +CLA: 80, INS: 24, P1: 40, P2: 00, Lc: 49, 86, 01, 01, 58, 20, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 30, 30, 58, 20, 30, 30, 30, 31, 31, 31, 32, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 30, 30, 02, 00, Le: 00, SW1: 90, SW2: 00 CLA: 80, INS: 1e, P1: 40, P2: 00, Lc: 00, Le: 21, 83, 02, 57, 4a, 61, 76, 61, 63, 61, 72, 64, 4b, 65, 79, 6d, 61, 73, 74, 65, 72, 44, 65, 76, 69, 63, 65, 46, 47, 6f, 6f, 67, 6c, 65, SW1: 90, SW2: 00 CLA: 80, INS: 18, P1: 40, P2: 00, Lc: 23, 81, 58, 20, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 1a, 1b, 1c, 1d, 1e, 1f, Le: 00, SW1: 90, SW2: 00 +CLA: 80, INS: 10, P1: 40, P2: 00, Lc: 22, 81, a4, 1a, 10, 00, 00, 02, 01, 1a, 30, 00, 00, 03, 19, 08, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, Le: 0d, 83, 00, 58, d4, 85, 58, 40, dc, 1d, ed, 50, 93, 56, 0c, 97, de, e7, 37, 82, 41, be, cc, 97, 19, 46, 6f, 18, 3e, 55, 1d, 95, b5, a2, 32, d6, 8d, c3, f9, ab, 16, e5, 8d, 7f, 8a, fe, 09, 86, f0, 83, 04, 50, ef, e1, 8d, 4e, d3, a9, 53, 99, b8, d4, bc, ad, 8f, a2, 5e, 8a, 84, 1a, ea, 29, 4c, 00, 47, 73, 4c, ad, c1, 48, a8, 52, e8, 13, dd, 4c, 48, c7, 53, a4, 74, a5, 3d, 80, 5c, 1a, ab, 92, 82, a0, a7, 1a, 10, 00, 00, 02, 01, 1a, 30, 00, 00, 03, 19, 08, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 58, 40, b4, 05, c4, 0e, 08, 15, 0e, a5, 86, 84, b2, 03, 00, 2c, cb, d7, 23, 85, c9, c1, 0b, 74, cb, 20, 10, b1, 25, 8e, 4b, 38, d4, 72, da, 2c, 2c, 64, 0e, e9, 2f, b2, d6, 74, 2e, 02, 77, 69, 74, 9f, a9, 98, e7, 7a, 4f, 9e, d4, 06, 62, 0c, 33, 6e, 80, 3a, 3f, e3, 82, a0, a7, 1a, 10, 00, 00, 02, 01, 1a, 30, 00, 00, 03, 19, 08, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 10, P1: 40, P2: 00, Lc: 23, 81, a4, 1a, 10, 00, 00, 02, 18, 20, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, Le: 01, 03, SW1: 90, SW2: 00 +CLA: 80, INS: 10, P1: 40, P2: 00, Lc: 22, 81, a4, 1a, 10, 00, 00, 02, 03, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, Le: d6, 83, 00, 58, 9d, 85, 58, 18, ac, 08, ac, 35, c5, b0, 8e, f4, 8a, 74, 5a, b7, ce, 25, 5e, 1e, 65, 8f, 17, ad, 86, 5f, 58, 4a, 4c, 7d, 9f, c1, 98, e9, 17, 75, c2, ca, c2, 4a, 74, 4c, 38, 78, 4e, 7e, 14, 6c, 55, dc, 46, 1b, da, 0e, 82, a0, a7, 1a, 10, 00, 00, 02, 03, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 58, 31, 04, 5a, 10, b8, d7, e9, be, 0a, e9, 47, 6c, 79, e4, 64, 15, 9c, 9d, 32, c6, 74, dd, 17, 11, 61, b7, 9a, 0c, 57, 8e, a1, 5f, 64, 1e, 8d, 51, 5b, 1e, 19, 4e, cc, 0e, 33, 15, d0, 9e, f6, f5, 8d, 2e, 82, a0, a7, 1a, 10, 00, 00, 02, 03, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 10, P1: 40, P2: 00, Lc: 22, 81, a4, 1a, 10, 00, 00, 02, 18, 21, 1a, 30, 00, 00, 03, 18, a8, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 20, 00, 00, 01, 42, 02, 03, Le: 01, 03, SW1: 90, SW2: 00 +CLA: 80, INS: 10, P1: 40, P2: 00, Lc: 32, 81, a6, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 30, 00, 00, 08, 18, 80, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, Le: a6, 83, 00, 58, 67, 84, 50, c9, 2e, be, 94, f0, 2b, e7, 57, 9d, 90, 5b, b0, 08, 1c, b7, 93, 4c, ae, 8e, 3e, 17, 1d, 67, 15, cc, 16, e8, f2, c4, 4c, de, 14, d8, 8e, 94, 0e, 50, 63, ee, 34, 4e, 4f, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 00, 08, 18, 80, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 00, 08, 18, 80, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 1a, 10, 00, 02, be, 00, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 11, P1: 40, P2: 00, Lc: b4, 83, a5, 1a, 10, 00, 00, 02, 01, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 70, 00, 01, f7, 01, 03, 58, 85, 82, 58, 40, 80, 9a, 87, 4c, e1, a0, 71, 44, ae, 45, dd, 8d, 1c, 05, b4, d0, 44, 23, dd, 42, a7, c9, 53, 44, ac, 31, 4a, 22, 4a, 02, 65, a0, aa, 21, a8, 30, 94, 7d, 13, a1, bc, 89, 81, b5, 54, de, 75, 82, b9, 0b, 1a, 7a, 81, 0c, 51, e0, 2f, 91, 97, d4, e8, 33, 27, 61, 58, 40, 92, 6c, 79, 17, bb, 36, 6f, b7, 58, 25, 84, 98, a9, 56, 07, e6, 07, f6, 26, 92, 15, f6, 21, 9f, 6c, f0, b4, e7, 20, 42, ac, b6, d8, 30, 61, 06, c9, 3b, 30, 67, 1e, 8d, 74, 11, 8b, 06, 98, ab, 8d, 6a, 6c, cd, b7, 2f, c3, a8, 30, c7, 68, 03, 4f, 72, c7, 5b, Le: 1d, 83, 00, 58, dc, 85, 58, 40, 47, 58, 70, e8, fd, 81, ea, a4, eb, 96, 3b, 69, 1b, 6d, fe, b2, 9d, 99, 4c, dd, 42, 4c, 49, dc, 8d, 98, bc, ca, 2a, d1, 51, 2b, ad, ab, f1, 3f, 8e, 8e, db, f1, 6a, ec, 87, 36, af, c3, b2, 9e, ba, 14, 92, c9, 84, d2, 96, 5c, fa, 73, 02, 98, 49, 08, 7e, 33, 4c, 11, 7d, e1, 4b, ab, 5e, d5, 21, e2, d5, d0, b9, 4c, a6, 49, cb, d7, 6f, 01, 98, cd, 8e, c4, 74, 8a, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 01, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 70, 00, 01, f7, 01, 1a, 30, 00, 00, 03, 19, 08, 00, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 58, 40, 92, 6c, 79, 17, bb, 36, 6f, b7, 58, 25, 84, 98, a9, 56, 07, e6, 07, f6, 26, 92, 15, f6, 21, 9f, 6c, f0, b4, e7, 20, 42, ac, b6, d8, 30, 61, 06, c9, 3b, 30, 67, 1e, 8d, 74, 11, 8b, 06, 98, ab, 8d, 6a, 6c, cd, b7, 2f, c3, a8, 30, c7, 68, 03, 4f, 72, c7, 5b, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 01, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 70, 00, 01, f7, 01, 1a, 30, 00, 00, 03, 19, 08, 00, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 11, P1: 40, P2: 00, Lc: 7d, 83, a4, 1a, 10, 00, 00, 02, 03, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 03, 58, 54, 83, 58, 18, a6, 68, de, ec, 65, 6c, fb, ee, aa, 43, ef, 97, 9d, 10, 82, f0, 99, 5f, 10, f3, ee, 9c, 38, 57, 58, 31, 04, 3a, f8, f4, fa, 1f, e4, 4d, 62, a1, cd, 26, 8e, 1a, 5a, aa, f5, a8, 94, e3, 8b, 4c, ce, 49, a1, 57, 25, 81, 6d, be, 5c, 3b, 07, 95, b6, 89, 24, 6e, 9d, 25, 22, e6, 5f, 41, cc, 59, ce, 25, 0c, 1a, 10, 00, 00, 0a, 01, Le: e6, 83, 00, 58, a5, 85, 58, 18, a3, b5, 4b, b2, 54, 26, 92, ba, 13, e7, 2a, 8c, 04, fa, 7e, 5c, 74, 1e, 12, fb, 6f, 59, 7a, 27, 4c, c7, dd, 10, 77, 31, 33, 35, 0a, 83, 1d, 8b, f1, 4c, 13, 9b, 59, 6d, 06, 55, 6e, e4, a8, c2, bf, 69, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 03, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 10, 00, 00, 0a, 01, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 58, 31, 04, 3a, f8, f4, fa, 1f, e4, 4d, 62, a1, cd, 26, 8e, 1a, 5a, aa, f5, a8, 94, e3, 8b, 4c, ce, 49, a1, 57, 25, 81, 6d, be, 5c, 3b, 07, 95, b6, 89, 24, 6e, 9d, 25, 22, e6, 5f, 41, cc, 59, ce, 25, 0c, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 03, 1a, 50, 00, 00, c8, 1a, 00, 01, 00, 01, 1a, 30, 00, 00, 03, 19, 01, 00, 1a, 10, 00, 00, 0a, 01, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 11, P1: 40, P2: 00, Lc: 3f, 83, a5, 1a, 10, 00, 00, 02, 18, 20, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 03, 52, 81, 50, 95, e6, 79, 36, 64, a5, ec, 72, bf, 01, 4c, 83, 6c, cd, cf, 51, Le: 01, 0c, SW1: 90, SW2: 00 +CLA: 80, INS: 11, P1: 40, P2: 00, Lc: 46, 83, a6, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 1a, 30, 00, 00, 08, 18, 80, 03, 52, 81, 50, fc, a6, 8f, 58, 68, 93, de, d0, c0, 74, 1c, 6f, 1d, 39, 2e, 4a, Le: a6, 83, 00, 58, 67, 84, 50, 89, ba, 5c, 77, e1, 0d, 5f, a8, 79, 30, 46, 0a, 77, 68, f7, 95, 4c, 70, dd, 86, 23, 76, 5a, 88, cb, f1, 89, 32, 38, 4c, e2, 41, 13, e8, 3e, 7a, 31, f9, dc, 2c, ae, af, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 1a, 30, 00, 00, 08, 18, 80, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, 82, a1, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, a7, 1a, 10, 00, 00, 02, 18, 80, 1a, 30, 00, 00, 03, 18, 80, 1a, 20, 00, 00, 05, 41, 04, 1a, 30, 00, 00, 08, 18, 80, 1a, 10, 00, 02, be, 02, 1a, 30, 00, 02, c1, 01, 1a, 30, 00, 02, c2, 01, SW1: 90, SW2: 00 +CLA: 80, INS: 11, P1: 40, P2: 00, Lc: 3f, 83, a5, 1a, 10, 00, 00, 02, 18, 21, 1a, 30, 00, 01, f5, 1a, 01, 02, 03, 04, 1a, 90, 00, 02, 59, 44, 54, 65, 73, 74, 1a, 30, 00, 00, 03, 18, a8, 1a, 20, 00, 00, 05, 41, 04, 03, 52, 81, 50, 8b, d4, d5, 84, 37, 39, c0, 1b, db, ed, 3c, 68, 99, 3a, dc, 3d, Le: 01, 0c, SW1: 90, SW2: 00