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..67af5549 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMCipherImpl.java @@ -0,0 +1,172 @@ +package com.android.javacard.keymaster; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacardx.crypto.Cipher; +import javax.crypto.AEADBadTagException; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + + +public class KMCipherImpl extends KMCipher{ + private Cipher cipher; + private javax.crypto.Cipher sunCipher; + private short cipherAlg; + private short paddingAlg; + private short mode; + private boolean verificationFlag; + public static short aes_gcm_decrypt_final_data = 0x00; + + KMCipherImpl(Cipher c){ + cipher = c; + } + KMCipherImpl(javax.crypto.Cipher c){ + sunCipher = c; + } + + @Override + public short doFinal(byte[] buffer, short startOff, short length, byte[] scratchPad, short i){ + if(cipherAlg == KMCipher.CIPHER_RSA && + (paddingAlg == KMCipher.PAD_PKCS1_OAEP_SHA256||paddingAlg == KMCipher.PAD_PKCS1_OAEP)){ + try { + return (short)sunCipher.doFinal(buffer,startOff,length,scratchPad,i); + } 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); + } + }else if(cipherAlg == KMCipher.ALG_AES_GCM){ + try { + /* + if (mode == javax.crypto.Cipher.DECRYPT_MODE) { + short acutalLen = (short)sunCipher.getOutputSize(length); + aes_gcm_decrypt_final_data = KMByteBlob.instance(acutalLen); + return (short)sunCipher.doFinal(buffer,startOff,length, + KMByteBlob.cast(aes_gcm_decrypt_final_data).getBuffer(), + KMByteBlob.cast(aes_gcm_decrypt_final_data).getStartOff()); + } + */ + return (short)sunCipher.doFinal(buffer,startOff,length,scratchPad,i); + } catch (AEADBadTagException e) { + e.printStackTrace(); + verificationFlag = false; + KMException.throwIt(KMError.VERIFICATION_FAILED); + } catch (ShortBufferException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (IllegalBlockSizeException e) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (BadPaddingException e) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + } else if(cipherAlg == KMCipher.ALG_AES_CTR){ + try { + return (short)sunCipher.doFinal(buffer,startOff,length,scratchPad,i); + } 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); + } + } + else{ + short len = cipher.doFinal(buffer, startOff, length, scratchPad, i); + // JCard Sim removes leading zeros during decryption in case of no padding - we add that back. + if (cipherAlg == Cipher.ALG_RSA_NOPAD && mode == Cipher.MODE_DECRYPT && len < 256) { + byte[] tempBuf = new byte[256]; + Util.arrayFillNonAtomic(tempBuf, (short) 0, (short) 256, (byte) 0); + Util.arrayCopyNonAtomic(scratchPad, (short) 0, tempBuf, (short) (i + 256 - len), len); + Util.arrayCopyNonAtomic(tempBuf, (short) 0, scratchPad, i, (short) 256); + len = 256; + } + return len; + } + return KMType.INVALID_VALUE; + } + + @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) { + short len = 0; + if(cipherAlg == KMCipher.ALG_AES_GCM || cipherAlg == KMCipher.ALG_AES_CTR){ + try { + return (short)sunCipher.update(buffer,startOff,length,scratchPad,i); + } catch (ShortBufferException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + } else{ + return cipher.update(buffer, startOff, length, scratchPad, i); + } + return KMType.INVALID_VALUE; + } + + @Override + public void updateAAD(byte[] buffer, short startOff, short length) { + try { + sunCipher.updateAAD(buffer,startOff,length); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (IllegalStateException e) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (UnsupportedOperationException e) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + } + + @Override + public short getPaddingAlgorithm() { + return paddingAlg; + } + + @Override + public void setPaddingAlgorithm(short alg) { + paddingAlg = alg; + } + + public short getMode() { + return mode; + } + + public void setMode(short mode) { + this.mode = mode; + } + + @Override + public short getCipherProvider() { + return KMCipher.SUN_JCE; + } + + @Override + public short getAesGcmOutputSize(short len, short macLength) { + if (sunCipher != null) { + return (short) sunCipher.getOutputSize(len); + } else { + if (mode == KMType.ENCRYPT) { + return (short) (len + macLength); + } else { + return (short) (len - macLength); + } + } + } +} diff --git a/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMEcdsa256NoDigestSignature.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMEcdsa256NoDigestSignature.java new file mode 100644 index 00000000..e33ebf69 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMEcdsa256NoDigestSignature.java @@ -0,0 +1,178 @@ +package com.android.javacard.keymaster; + +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SignatureException; +import java.security.interfaces.ECPrivateKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.Signature; + + +public class KMEcdsa256NoDigestSignature extends Signature { + private java.security.Signature sunSigner; + + public KMEcdsa256NoDigestSignature(byte mode, byte[] key, short keyStart, short keyLength){ + KeyFactory kf; + try { + sunSigner = java.security.Signature.getInstance("NONEwithECDSA", "SunEC"); + kf = KeyFactory.getInstance("EC"); + AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC", "SunEC"); + //Supported curve secp256r1 + parameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); + if(mode == Signature.MODE_SIGN) { + byte[] privKey = new byte[keyLength]; + for(short i =0; i< keyLength; i++) { + privKey[i] = key[keyStart+i]; + } + BigInteger bI = new BigInteger(privKey); + ECPrivateKeySpec prikeyspec = new ECPrivateKeySpec(bI, ecParameters); + ECPrivateKey privkey = (ECPrivateKey) kf.generatePrivate(prikeyspec); + sunSigner.initSign(privkey); + } else { + //Check if the first byte is 04 and remove it. + if(key[keyStart] == 0x04) { + //uncompressed format. + keyStart++; + keyLength--; + } + short i = 0; + byte[] pubx = new byte[keyLength/2]; + for(;i < keyLength/2; i++ ) { + pubx[i] = key[keyStart+i]; + } + byte[] puby = new byte[keyLength/2]; + for(i = 0;i < keyLength/2; i++ ) { + puby[i] = key[keyStart+keyLength/2+i]; + } + BigInteger bIX = new BigInteger(pubx); + BigInteger bIY = new BigInteger(puby); + ECPoint point = new ECPoint(bIX, bIY); + ECPublicKeySpec pubkeyspec = new ECPublicKeySpec(point, ecParameters); + ECPublicKey pubkey = (ECPublicKey) kf.generatePublic(pubkeyspec); + sunSigner.initVerify(pubkey); + } + } catch (NoSuchAlgorithmException e) { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } catch (NoSuchProviderException e) { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } catch(InvalidParameterSpecException e) { + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch(InvalidKeySpecException e) { + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch(InvalidKeyException e) { + CryptoException.throwIt(CryptoException.INVALID_INIT); + } + } + + @Override + public void init(Key key, byte b) throws CryptoException { + + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + + } + + @Override + public byte getAlgorithm() { + return 0; + } + + @Override + public byte getMessageDigestAlgorithm() { + return 0; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { + byte[] msgBytes = new byte[messageLength]; + for(int i =0; i< messageLength; i++) { + msgBytes[i] = message[msgStart+i]; + } + try { + if(messageLength > 0) + sunSigner.update(msgBytes); + } catch (SignatureException e) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) throws CryptoException { + short len = 0; + try { + update(bytes, i , i1); + byte[] sig = sunSigner.sign(); + Util.arrayCopyNonAtomic(sig, (short)0, bytes1, i2, (short)sig.length); + return (short)sig.length; + } catch (SignatureException e) { + // TODO Auto-generated catch block + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + return len; + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Cannot support this method as javacard cipher api does not allow 256 byte for public key + // encryption without padding. It only allows 255 bytes data. + try { + update(bytes, i , i1); + return sunSigner.verify(bytes1, i2, i3); + } catch (SignatureException e) { + // TODO Auto-generated catch block + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + return false; + } + + @Override + public boolean verifyPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + return false; + } +} 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..72b6f5d9 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMJcardSimulator.java @@ -0,0 +1,1143 @@ +/* + * 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.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.RSAPrivateKeySpec; +import java.security.spec.RSAPublicKeySpec; +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.IvParameterSpec; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +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 KMSEProvider { + 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 short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + return 0; + } + + @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 + byte[] keyMaterial = new byte[16]; + short keySize = 16; + if(key.getSize() == 128){ + keyMaterial = new byte[16]; + }else if(key.getSize() == 256){ + keyMaterial = new byte[32]; + keySize = 32; + } + key.getKey(keyMaterial,(short)0); + //print("KeyMaterial Enc", keyMaterial); + //print("Authdata Enc", authData, authDataStart, authDataLen); + java.security.Key aesKey = new SecretKeySpec(keyMaterial,(short)0,keySize, "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 + byte[] keyMaterial = new byte[16]; + short keySize = 16; + if(key.getSize() == 128){ + keyMaterial = new byte[16]; + }else if(key.getSize() == 256){ + keyMaterial = new byte[32]; + keySize = 32; + } + 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)keySize, "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(authTagLen * 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 + authTagLen)]; + Util.arrayCopyNonAtomic(encSecret,encSecretStart,inputBuf,(short)0,encSecretLen); + Util.arrayCopyNonAtomic(authTag,authTagStart,inputBuf,encSecretLen,authTagLen); + // 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 cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength, byte[] keyBuf, short keyStart) { + HMACKey key = cmacKdf(keyMaterial,label,context,contextStart,contextLength); + return key.getKey(keyBuf,keyStart); + } + + @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 short hmacSign(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + return 0; + } + + @Override + public boolean hmacVerify(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart, short macLength) { + return false; + } + + @Override + public short initSymmetricOperation(byte purpose, byte alg, byte digest, byte padding, byte blockMode, byte[] keyBuf, short keyStart, short keyLength) { + return 0; + } + + @Override + public short initSymmetricOperation(byte purpose, byte alg, byte digest, byte[] keyBuf, short keyStart, short keyLength) { + return 0; + } + + @Override + public short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, byte[] privKeyBuf, short privKeyStart, short privKeyLength, byte[] modBuf, short modStart, short modLength) { + return 0; + } + + @Override + public short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, byte[] privKeyBuf, short privKeyStart, short privKeyLength) { + return 0; + } + + @Override + public short updateOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { + return 0; + } + + @Override + public short finishOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { + return 0; + } + + @Override + public void abortOperation(short opHandle) { + + } + + @Override + public short hmacInit(byte[] keyBuf, short keyStart, short keyLength, byte digest, byte mode) { + return 0; + } + + @Override + public short hmacSign(short opHandle, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + return 0; + } + + @Override + public boolean hmacVerify(short opHandle, byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart, short macLength) { + return false; + } + + @Override + public short hmacUpdate(short opHandle, byte[] dataBuf, short dataStart, short dataLength) { + return 0; + } + + @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 || padding == KMCipher.PAD_PKCS1_OAEP) { + return createRsaOAEP256Cipher(Cipher.MODE_DECRYPT,(byte)padding,secret,secretStart,secretLength,modBuffer,modOff,modLength); + }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); + KMCipherImpl inst = new KMCipherImpl(rsaCipher); + inst.setCipherAlgorithm(cipherAlg); + inst.setMode(Cipher.MODE_DECRYPT); + inst.setPaddingAlgorithm(padding); + return inst; + } + + private KMCipher createRsaOAEP256Cipher(byte mode,byte padding, + byte[] secret, short secretStart, short secretLen, + byte[] modBuffer, short modOff, short modLength) { + // Convert byte arrays into keys + byte[] exp = null; + byte[] mod = new byte[modLength]; + if (secret != null) { + exp = new byte[secretLen]; + Util.arrayCopyNonAtomic(secret, secretStart, exp, (short) 0, secretLen); + }else{ + exp = new byte[]{0x01,0x00,0x01}; + } + Util.arrayCopyNonAtomic(modBuffer,modOff,mod,(short)0,modLength); + String modString = toHexString(mod); + String expString = toHexString(exp); + BigInteger modInt = new BigInteger(modString,16); + BigInteger expInt = new BigInteger(expString,16); + javax.crypto.Cipher rsaCipher = null; + try{ + KeyFactory kf = KeyFactory.getInstance("RSA"); + // Create cipher with oaep padding + OAEPParameterSpec oaepSpec = null; + if(padding == KMCipher.PAD_PKCS1_OAEP_SHA256){ + oaepSpec= new OAEPParameterSpec("SHA-256", "MGF1", + MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); + }else{ + oaepSpec= new OAEPParameterSpec("SHA1", "MGF1", + MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); + } + rsaCipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPPadding", "SunJCE"); + if (mode == KMCipher.MODE_ENCRYPT){ + RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modInt, expInt); + java.security.interfaces.RSAPublicKey pubKey = (java.security.interfaces.RSAPublicKey) kf.generatePublic(pubSpec); + rsaCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, pubKey, oaepSpec); + } else { + RSAPrivateKeySpec privSpec = new RSAPrivateKeySpec(modInt, expInt); + java.security.interfaces.RSAPrivateKey privKey = (java.security.interfaces.RSAPrivateKey) kf.generatePrivate(privSpec); + rsaCipher.init(javax.crypto.Cipher.DECRYPT_MODE, privKey, oaepSpec); + } + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } catch (InvalidKeySpecException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (InvalidKeyException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (NoSuchPaddingException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } catch (NoSuchProviderException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } + KMCipherImpl ret = new KMCipherImpl(rsaCipher); + ret.setCipherAlgorithm(KMCipher.CIPHER_RSA); + ret.setPaddingAlgorithm(padding); + ret.setMode(mode); + return ret; + } + private String toHexString(byte[] num){ + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < num.length; i++){ + sb.append(String.format("%02X", num[i])) ; + } + return sb.toString(); + } + @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 (padding == KMCipher.PAD_NOPAD || + (padding == KMCipher.PAD_PKCS1 && msgDigestAlg == MessageDigest.ALG_NULL)) { + return createNoDigestSigner(padding,secret, secretStart, secretLength, + modBuffer, modOff, modLength); + } + 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; + } + + private Signature createNoDigestSigner(short padding, + byte[] secret, short secretStart, short secretLength, + byte[] modBuffer, short modOff, short modLength) { + Cipher rsaCipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD,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); + KMRsa2048NoDigestSignature inst = new KMRsa2048NoDigestSignature(rsaCipher,(byte)padding, + modBuffer,modOff,modLength); + return inst; + } + + @Override + public Signature createEcSigner(short msgDigestAlg, byte[] secret, short secretStart, short secretLength) { + short alg = Signature.ALG_ECDSA_SHA_256; + Signature ecSigner; + if(msgDigestAlg == MessageDigest.ALG_NULL) { + //CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + ecSigner = new KMEcdsa256NoDigestSignature(Signature.MODE_SIGN, secret, secretStart, secretLength); + } else { + //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); + 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); + //TODO Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + //While sending back the iv send only 8 bytes. + symmCipher.init(key, (byte) mode, ivBuffer, ivStart, (short)8); + 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; + case KMCipher.ALG_AES_CTR: // uses SUNJCE + return createAesCtrCipherNoPad(mode, secret,secretStart,secretLength,ivBuffer,ivStart,ivLength); + default://This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + KMCipherImpl cipher = new KMCipherImpl(symmCipher); + cipher.setCipherAlgorithm(cipherAlg); + cipher.setPaddingAlgorithm(padding); + cipher.setMode(mode); + return cipher; + } + + private KMCipher createAesCtrCipherNoPad(short mode, byte[] secret, short secretStart, short secretLength, byte[] ivBuffer, short ivStart, short ivLength) { + if(secretLength != 16 && secretLength != 32){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if(ivLength != 16){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if(mode != KMCipher.MODE_ENCRYPT && mode != KMCipher.MODE_DECRYPT){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + //Create the sun jce compliant aes key + byte[] keyMaterial = new byte[secretLength]; + Util.arrayCopyNonAtomic(secret,secretStart,keyMaterial,(short)0,secretLength); + java.security.Key aesKey = new SecretKeySpec(keyMaterial,(short)0,keyMaterial.length, "AES"); + // Create the cipher + javax.crypto.Cipher cipher = null; + try { + cipher = javax.crypto.Cipher.getInstance("AES/CTR/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 + byte[] iv = new byte[ivLength]; + Util.arrayCopyNonAtomic(ivBuffer,ivStart,iv,(short)0,ivLength); + // Init Cipher + IvParameterSpec ivSpec = new IvParameterSpec(iv); + try { + if(mode == KMCipher.MODE_ENCRYPT)cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, ivSpec); + else cipher.init(javax.crypto.Cipher.DECRYPT_MODE, aesKey, ivSpec); + } catch (InvalidKeyException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + KMCipherImpl ret = new KMCipherImpl(cipher); + ret.setCipherAlgorithm(KMCipher.ALG_AES_CTR); + ret.setMode(mode); + ret.setPaddingAlgorithm((short)0); + return ret; + } + + @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 createAesGcmCipher(short mode, short tagLen, byte[] secret, short secretStart, short secretLength, + byte[] ivBuffer, short ivStart, short ivLength) { + if(secretLength != 16 && secretLength != 32){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if(ivLength != AES_GCM_NONCE_LENGTH){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if(mode != KMCipher.MODE_ENCRYPT && mode != KMCipher.MODE_DECRYPT){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + //Create the sun jce compliant aes key + byte[] keyMaterial = new byte[secretLength]; + Util.arrayCopyNonAtomic(secret,secretStart,keyMaterial,(short)0,secretLength); + //print("KeyMaterial Enc", keyMaterial); + //print("Authdata Enc", authData, authDataStart, authDataLen); + java.security.Key aesKey = new SecretKeySpec(keyMaterial,(short)0,keyMaterial.length, "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 + byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; + Util.arrayCopyNonAtomic(ivBuffer,ivStart,iv,(short)0,AES_GCM_NONCE_LENGTH); + // Init Cipher + GCMParameterSpec spec = new GCMParameterSpec(tagLen, iv,(short)0,AES_GCM_NONCE_LENGTH); + try { + if(mode == KMCipher.MODE_ENCRYPT)mode = javax.crypto.Cipher.ENCRYPT_MODE; + else mode = javax.crypto.Cipher.DECRYPT_MODE; + cipher.init(mode, aesKey, spec); + } catch (InvalidKeyException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.INVALID_INIT); + } catch (InvalidAlgorithmParameterException e) { + e.printStackTrace(); + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + KMCipherImpl ret = new KMCipherImpl(cipher); + ret.setCipherAlgorithm(KMCipher.ALG_AES_GCM); + ret.setMode(mode); + ret.setPaddingAlgorithm((short)0); + return ret; + } + + @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; + if (padding == KMCipher.PAD_PKCS1_OAEP_SHA256 || padding == KMCipher.PAD_PKCS1_OAEP) { + return createRsaOAEP256Cipher(Cipher.MODE_ENCRYPT,(byte)padding,null,(short)0,(short)0,modBuffer,modOff,modLength); + }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); + KMCipherImpl inst = new KMCipherImpl(rsaCipher); + inst.setCipherAlgorithm(cipherAlg); + inst.setMode(Cipher.MODE_ENCRYPT); + inst.setPaddingAlgorithm(padding); + return inst; + } + + + @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; + Signature ecVerifier; + //if(msgDigestAlg == MessageDigest.ALG_NULL) CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + if(msgDigestAlg == MessageDigest.ALG_NULL) { + //CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + ecVerifier = new KMEcdsa256NoDigestSignature(Signature.MODE_VERIFY, pubKey, pubKeyStart, pubKeyLength); + } else { + // 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); + ecVerifier = Signature.getInstance((byte)alg,false); + ecVerifier.init(key,Signature.MODE_VERIFY); + } + return ecVerifier; + } + + @Override + public short getSystemTimeInMilliSeconds(byte[] timeBuf, short timeStart, short timeOffset) { + return 0; + } + + @Override + public short addListener(KMEventListener listener, byte eventType) { + return 0; + } + + @Override + public short getEventData(byte[] eventBuf, short eventStart, short eventLength) { + return 0; + } + + @Override + public boolean isAlgSupported(byte alg) { + return true; + } + + @Override + public boolean isKeySizeSupported(byte alg, short keySize) { + return true; + } + + @Override + public boolean isCurveSupported(byte eccurve) { + return true; + } + + @Override + public boolean isDigestSupported(byte alg, byte digest) { + return true; + } + + @Override + public boolean isPaddingSupported(byte alg, byte padding) { + return true; + } + + @Override + public boolean isBlockModeSupported(byte alg, byte blockMode) { + return true; + } + + @Override + public boolean isSystemTimerSupported() { + return false; + } + + @Override + public boolean isBootEventSupported() { + return false; + } + + /* + 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/JCardSimProvider/com/android/javacard/keymaster/KMRsa2048NoDigestSignature.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMRsa2048NoDigestSignature.java new file mode 100644 index 00000000..1aff36eb --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMRsa2048NoDigestSignature.java @@ -0,0 +1,123 @@ +package com.android.javacard.keymaster; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +public class KMRsa2048NoDigestSignature extends Signature { + private Cipher inst; // ALG_RSA_NOPAD. + private byte padding; + private byte[] rsaModulus; // to compare with the data value + + public KMRsa2048NoDigestSignature(Cipher ciph, byte padding, byte[]mod, short start, short len){ + inst = ciph; + this.padding = padding; + if(len != 256) CryptoException.throwIt(CryptoException.INVALID_INIT); + rsaModulus = new byte[256]; + Util.arrayCopyNonAtomic(mod,start,rsaModulus,(short)0,len); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + + } + + @Override + public byte getAlgorithm() { + return 0; + } + + @Override + public byte getMessageDigestAlgorithm() { + return 0; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] bytes, short i, short i1) throws CryptoException { + + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) throws CryptoException { + byte[] inputData = padData(bytes,i,i1); + return inst.doFinal(inputData,(short)0,(short)256,bytes1,i2); + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Cannot support this method as javacard cipher api does not allow 256 byte for public key + // encryption without padding. It only allows 255 bytes data. + return false; + } + + @Override + public boolean verifyPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + return false; + } + + private byte[] padData(byte[] buf, short start, short len){ + byte[] inputData = new byte[256]; + if(!isValidData(buf, start,len)){ + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(inputData, (short) 0, (short) 256, (byte) 0x00); + if (padding == KMCipher.PAD_NOPAD) { // add zero to right + } else if (padding == KMCipher.PAD_PKCS1) {// 0x00||0x01||PS||0x00 + inputData[0] = 0x00; + inputData[1] = 0x01; + Util.arrayFillNonAtomic(inputData,(short)2,(short)(256-len-3),(byte)0xFF); + inputData[(short)(256-len-1)] = 0x00; + }else{ + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + Util.arrayCopyNonAtomic(buf,start,inputData,(short)(256 -len),len); + return inputData; + } + + private boolean isValidData(byte[] buf, short start, short len) { + if (padding == KMCipher.PAD_NOPAD) { + if (len > 256) return false; + else if (len == 256) { + short v = Util.arrayCompare(buf, start, rsaModulus, (short) 0, len); + if (v > 0) return false; + } + } else {//pkcs1 no digest + if(len > 245){ + return false; + } + } + return true; + } +} diff --git a/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java new file mode 100644 index 00000000..34628ba7 --- /dev/null +++ b/Applet/Applet/JCardSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java @@ -0,0 +1,8 @@ + +package com.android.javacard.keymaster; + +public class KMSEProviderImpl { + public static KMSEProvider instance(){ + return new KMJcardSimulator(); + } +} 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..34e1851e --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMCipherImpl.java @@ -0,0 +1,57 @@ +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 getCipherProvider() { + return 0; + } + + @Override + public short getAesGcmOutputSize(short len, short macLength) { + return len; + } + + @Override + public short update(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { + return cipher.update(buffer,startOff,length,scratchPad,i); + } + @Override + public void updateAAD(byte[] buffer, short startOff, short length) { + } + + @Override + public short getPaddingAlgorithm() { + return cipher.getPaddingAlgorithm(); + } +} diff --git a/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java new file mode 100644 index 00000000..9920c4ee --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSEProviderImpl.java @@ -0,0 +1,8 @@ + +package com.android.javacard.keymaster; + +public class KMSEProviderImpl { + public static KMSEProvider 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..f7868951 --- /dev/null +++ b/Applet/Applet/OracleSimProvider/com/android/javacard/keymaster/KMSimulator.java @@ -0,0 +1,654 @@ +/* + * 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 KMSEProvider { + 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 short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + return 0; + } + + @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 cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength, byte[] keyBuf, short keyStart) { + return 0; + } + + @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 short hmacSign(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + return 0; + } + + @Override + public boolean hmacVerify(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart, short macLength) { + return false; + } + + @Override + public short initSymmetricOperation(byte purpose, byte alg, byte digest, byte padding, byte blockMode, byte[] keyBuf, short keyStart, short keyLength) { + return 0; + } + + @Override + public short initSymmetricOperation(byte purpose, byte alg, byte digest, byte[] keyBuf, short keyStart, short keyLength) { + return 0; + } + + @Override + public short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, byte[] privKeyBuf, short privKeyStart, short privKeyLength, byte[] modBuf, short modStart, short modLength) { + return 0; + } + + @Override + public short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, byte[] privKeyBuf, short privKeyStart, short privKeyLength) { + return 0; + } + + @Override + public short updateOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { + return 0; + } + + @Override + public short finishOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { + return 0; + } + + @Override + public void abortOperation(short opHandle) { + + } + + @Override + public short hmacInit(byte[] keyBuf, short keyStart, short keyLength, byte digest, byte mode) { + return 0; + } + + @Override + public short hmacSign(short opHandle, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + return 0; + } + + @Override + public boolean hmacVerify(short opHandle, byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart, short macLength) { + return false; + } + + @Override + public short hmacUpdate(short opHandle, byte[] dataBuf, short dataStart, short dataLength) { + return 0; + } + + @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 createAesGcmCipher(short mode, short tagLen, 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; + } + + @Override + public short getSystemTimeInMilliSeconds(byte[] timeBuf, short timeStart, short timeOffset) { + return 0; + } + + @Override + public short addListener(KMEventListener listener, byte eventType) { + return 0; + } + + @Override + public short getEventData(byte[] eventBuf, short eventStart, short eventLength) { + return 0; + } + + @Override + public boolean isAlgSupported(byte alg) { + return false; + } + + @Override + public boolean isKeySizeSupported(byte alg, short keySize) { + return false; + } + + @Override + public boolean isCurveSupported(byte eccurve) { + return false; + } + + @Override + public boolean isDigestSupported(byte alg, byte digest) { + return false; + } + + @Override + public boolean isPaddingSupported(byte alg, byte padding) { + return false; + } + + @Override + public boolean isBlockModeSupported(byte alg, byte blockMode) { + return false; + } + + @Override + public boolean isSystemTimerSupported() { + return false; + } + + @Override + public boolean isBootEventSupported() { + return false; + } + +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java b/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java index d2eed688..4646f46e 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -18,66 +18,78 @@ 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) { - ISOException.throwIt(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) { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - } - return vals[(short) (startOff + index)]; + public byte[] getBuffer() { + return heap; } } 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/KMBoolTag.java b/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java index 2e088893..ae0f75ac 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -18,8 +18,11 @@ 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, @@ -34,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)) { - ISOException.throwIt(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) { @@ -99,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 54b0e9e4..7f598eaa 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -20,93 +20,93 @@ 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)) { - ISOException.throwIt(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) { - ISOException.throwIt(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) { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - } - if (this.val == null) { - ISOException.throwIt(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) { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); - } - if (this.val == null) { - ISOException.throwIt(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 1569394c..a00d6c6f 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -18,8 +18,11 @@ 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, @@ -38,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(); - } - - @Override - public short getTagType() { - return KMType.BYTES_TAG; - } - - public static KMByteTag instance() { - return repository.newByteTag(); + public static short instance(short key) { + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return instance(key, KMByteBlob.exp()); } - public static KMByteTag instance(short key) { + public static short instance(short key, short byteBlob) { if (!validateKey(key)) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } - KMByteTag tag = repository.newByteTag(); - tag.key = key; - tag.val = null; - return tag; + 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 void create(KMByteTag[] byteTagRefTable) { - byte index = 0; - while (index < byteTagRefTable.length) { - byteTagRefTable[index] = new KMByteTag(); - index++; + 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); } - // create default assignBlob without any value - public static KMByteTag instance(short key, KMByteBlob array) { - if (!validateKey(key)) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } - KMByteTag tag = repository.newByteTag(); - tag.key = key; - tag.val = array; - return tag; + public short getKey() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+2)); + } + + public short getTagType() { + return KMType.BYTES_TAG; } - public KMByteTag withLength(short length) { - this.val.withLength(length); - return this; + public short getValue() { + return Util.getShort(heap, (short)(instPtr+TLV_HEADER_SIZE+4)); + } + + 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) { @@ -116,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..a36f11ab --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMCipher.java @@ -0,0 +1,44 @@ +package com.android.javacard.keymaster; + +public abstract class KMCipher { + public static final byte CIPHER_RSA = 7; + public static final short PAD_PKCS1_OAEP = 9; + 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 ALG_AES_GCM = -13; + 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 static final short ALG_AES_CTR = -16; + public static final short SUN_JCE = 0xE9; + + 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 void updateAAD(byte[] buffer, short startOff, short length); + + public abstract short getPaddingAlgorithm(); + + public abstract short getCipherAlgorithm(); + + public abstract void setPaddingAlgorithm(short alg); + + public abstract void setCipherAlgorithm(short alg); + + public abstract short getCipherProvider(); + + public abstract short getAesGcmOutputSize(short len, short macLength); +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java index a1c43aa3..a1ced7cd 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -19,7 +19,7 @@ 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; @@ -37,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; @@ -50,58 +49,206 @@ 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)) { - ISOException.throwIt(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) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } - if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { - ISOException.throwIt(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) { @@ -120,17 +267,30 @@ 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) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } short len = (short) (buffer[startOff] & ADDITIONAL_MASK); - byte enumVal = 0; + byte enumVal; if (len > UINT8_LENGTH) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } @@ -142,11 +302,11 @@ 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) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } @@ -156,156 +316,30 @@ private KMInteger decode(KMInteger exp) { } 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 { - inst = exp.instance(buffer, startOff, (short) 8); + 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) { - ISOException.throwIt(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)) { - ISOException.throwIt(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); - } - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - return null; - } - private short peekTagType() { if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java index ec736de4..c03e7201 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -18,6 +18,7 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; public class KMEncoder { @@ -43,177 +44,238 @@ 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; + //array{KMError.OK,Array{KMByteBlobs}} + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { + this.buffer = certBuffer; + this.startOff = certStart; + this.length = (short)(certStart+1); + //Array header - 2 elements i.e. 1 byte + this.startOff--; + // Error.Ok - 1 byte + this.startOff--; + //Array header - 2 elements i.e. 1 byte + this.startOff--; + // Cert Byte blob - typically 2 bytes length i.e. 3 bytes header + this.startOff -= 2; + if(certLength >= SHORT_PAYLOAD) { + this.startOff--; } - if(exp instanceof KMVerificationToken){ - encode((KMVerificationToken) exp); - return; + bufferStart = startOff; + if(this.startOff < bufferStart) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + writeMajorTypeWithLength(ARRAY_TYPE,(short)2); // Array of 2 elements + writeByte(UINT_TYPE); // Error.OK + writeMajorTypeWithLength(ARRAY_TYPE,(short)1); // Array of 1 element + writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length + return bufferStart; + } + + 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); } - ISOException.throwIt(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. @@ -222,34 +284,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); } } @@ -258,7 +322,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))); @@ -292,4 +355,15 @@ private void incrementStartOff(short inc){ ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } + /* + private static void print(byte[] buf, short start, short length){ + StringBuilder sb = new StringBuilder(); + for(int i = start; i < (start+length); i++){ + sb.append(String.format("%02X", buf[i])) ; + //if((i-start)%16 == 0 && (i-start) != 0) sb.append(String.format("\n")); + } + System.out.println(sb.toString()); + } + + */ } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java index 441fd833..0b3b1819 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -18,45 +18,64 @@ 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,ECCURVE}; 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)); } - public static KMEnum instance() { - return repository.newEnum(); + // 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(short enumType, byte val) { - KMEnum inst = 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 short instance(short enumType, byte val) { if (!validateEnum(enumType, val)) { 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[] { @@ -69,35 +88,37 @@ 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, UNVERIFIED_BOOT, FAILED_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}, + new byte[] {P_224, P_256, P_384, P_521} }; } - 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) { @@ -105,8 +126,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 @@ -123,4 +144,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 30c40bad..9d7c5012 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -18,90 +18,118 @@ 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; + } + + // 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; } - @Override - public void init() { - key = 0; - array = null; + 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) { - ISOException.throwIt(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) { @@ -111,41 +139,146 @@ 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 KMEnumArrayTag setValues(KMByteBlob val) { - this.array = val; - return this; + public short get(short index){ + return KMByteBlob.cast(getValues()).get(index); } - // instantiate enum array pointing to existing array. - public static KMEnumArrayTag instance(short key, KMByteBlob blob) { - // validate key - byte[] allowedVals = getAllowedEnumValues(key); - if (allowedVals == null) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); + + 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++; + } } - 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 false; + } + 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(); + } + 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 && digest != KMType.SHA1) 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) { - ISOException.throwIt(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; + case KMType.WRAP_KEY: + if(alg != KMType.RSA) 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 9a641425..b75563d6 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -18,8 +18,11 @@ 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 @@ -27,69 +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)){ - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } - KMEnumTag tag = repository.newEnumTag(); - tag.key = key; - return tag; + 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)) { - ISOException.throwIt(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/KMEventListener.java b/Applet/Applet/src/com/android/javacard/keymaster/KMEventListener.java new file mode 100644 index 00000000..e59972ab --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMEventListener.java @@ -0,0 +1,5 @@ +package com.android.javacard.keymaster; + +public interface KMEventListener { + void handle(byte event); +} diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMException.java b/Applet/Applet/src/com/android/javacard/keymaster/KMException.java index 3a26b047..1805a911 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMException.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMException.java @@ -16,13 +16,22 @@ package com.android.javacard.keymaster; -public class KMException { - - // 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 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; + } - public static boolean handle(short reason) { - return false; + public void clear(){ + reason = KMError.UNKNOWN_ERROR; } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java index 9c188c67..96b1ed9d 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -18,6 +18,7 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; +import javacard.framework.Util; public class KMHardwareAuthToken extends KMType { public static final byte CHALLENGE = 0x00; @@ -27,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) { - ISOException.throwIt(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 a1fca4d1..bc34c217 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -18,60 +18,78 @@ 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) { - ISOException.throwIt(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/KMInteger.java b/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java index 987a0858..e7f91ebf 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -18,117 +18,154 @@ 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; } - public byte[] getValue() { - return val; + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (instPtr + 1)); } - public KMInteger setValue(short val) { - this.val[2] = (byte) (val >> 8); - this.val[3] = (byte) (val & 0xFF); - return this; + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; } - public KMInteger setValue(byte[] val) { - this.val = val; - return this; + // Get the start of value + public short getStartOff() { + return (short) (instPtr + TLV_HEADER_SIZE); } - public short getShort() { - if (val == null) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } else if (val.length != 4) { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + public void getValue(byte[] dest, short destOff, short length){ + if(length < length()) KMException.throwIt(KMError.UNKNOWN_ERROR); + if(length > length()) { + length = length(); + destOff +=length; } - return Util.makeShort(val[2], val[3]); + 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() { + 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) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } else if (val.length != 4) { - ISOException.throwIt(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 > 8){ - ISOException.throwIt(ISO7816.SW_WRONG_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 { - return uint_64(num, srcOff); + 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 df96e114..c154f5a0 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -18,77 +18,96 @@ 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)) { - ISOException.throwIt(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)) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } - if (!(validateKey(key))) { - ISOException.throwIt(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) { @@ -101,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 d583f6b2..e3736793 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -18,9 +18,13 @@ 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, @@ -43,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)) { - ISOException.throwIt(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) { @@ -114,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 d4c1a9b5..0421e6da 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -18,60 +18,79 @@ 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) { - ISOException.throwIt(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..dff29d60 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -16,53 +16,380 @@ 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, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION + }; 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 0ee85ff1..32eda210 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -19,11 +19,20 @@ import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.AppletEvent; -import javacard.framework.CardRuntimeException; 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; /** @@ -35,9 +44,29 @@ // - remove this in future. public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength { // Constants. - private static final short MAX_LENGTH = (short) 0x1000; // 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 short KM_HAL_VERSION = (short) 0x4000; + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + private static final short MAX_IO_LENGTH = 0x600; + // "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. private static final byte ILLEGAL_STATE = 0x00; @@ -46,7 +75,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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; @@ -68,28 +96,112 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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; + private static final byte INS_DEVICE_LOCKED_CMD = 0x25; + private static final byte INS_EARLY_BOOT_ENDED_CMD = 0x26; - // GetHwInfo information - // TODO change this to just filling the buffer - private 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, - }; - private static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // 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; + + // 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; + // 64 bit unsigned calculations for time + private final static byte[] oneSecMsec = {0,0,0,0,0,0,0x03,(byte)0xE8};//1000 msec + private final static byte[] oneMinMsec = {0,0,0,0,0,0,(byte)0xEA,0x60};//60000 msec + private final static byte[] oneHourMsec = {0,0,0,0,0,0x36,(byte)0xEE,(byte)0x80};//3600000 msec + private final static byte[] oneDayMsec = {0,0,0,0,0x05,0x26,0x5C,0x00};//86400000 msec + private final static byte[] oneMonthMsec ={0,0,0,0,(byte)0x9A,0x7E,(byte)0xC8,0x00}; //2592000000 msec + private final static byte[] oneYearMsec = {0,0,0,0x07,0x57,(byte)0xB1,0x2C,0x00}; //31536000000 msec + // Leap year + 3 yrs + private final static byte[] fourYrsMsec = {0,0,0,0x1D,0x63,(byte)0xEB,0x0C,0x00};//126230400000 msec + private final static byte[] firstJan2020 = {0,0,0x01,0x6F,0x60,0x1E,0x5C,0x00}; //1577865600000 msec + private final static byte[] firstJan2051 = {0,0,0x02,0x53,0x27,(byte)0xC5,(byte)0x90,0x00}; // 2556172800000 msec - // State of the applet. - private KMEncoder encoder; - private KMDecoder decoder; - private KMRepository repository; - private byte keymasterState = ILLEGAL_STATE; - private byte[] buffer; + // Keymaster Applet attributes + private static byte keymasterState = ILLEGAL_STATE; + private static KMEncoder encoder; + private static KMDecoder decoder; + private static KMRepository repository; + private static KMSEProvider 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. */ protected KMKeymasterApplet() { + // TODO change this to make this compile time variation. + cryptoProvider = KMSEProviderImpl.instance(); + provisionDone = false; + setBootParamsDone = false; + byte[] buf = + JCSystem.makeTransientByteArray( + (short)32, JCSystem.CLEAR_ON_DESELECT); keymasterState = KMKeymasterApplet.INSTALL_STATE; - repository = KMRepository.instance(); - KMUtil.init(); + 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.MASTER_KEY_SIZE), + (short) 0, buf, (short) 0, repository.MASTER_KEY_SIZE); + repository.initMasterKey(buf, repository.MASTER_KEY_SIZE); + cryptoProvider.newRandomNumber(buf, (short) 0, repository.SHARED_SECRET_KEY_SIZE); + // TODO remove this when key agreement protocol is implemented. + repository.initHmacSharedSecretKey(buf, repository.SHARED_SECRET_KEY_SIZE); + // TODO currently hmac nonce is generated once when installing the applet. Remove this once boot + // signal reception is incorporated in the design. + cryptoProvider.newRandomNumber(buf, (short) 0, repository.HMAC_SEED_NONCE_SIZE); + repository.initHmacNonce(buf, (short)0, repository.HMAC_SEED_NONCE_SIZE); + // TODO Confirm before removing seed generation. + //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(); @@ -103,7 +215,7 @@ protected KMKeymasterApplet() { * @param bLength the length in bytes of the parameter data in bArray */ public static void install(byte[] bArray, short bOffset, byte bLength) { - KMKeymasterApplet keymaster = new KMKeymasterApplet(); + new KMKeymasterApplet(); } /** @@ -113,7 +225,7 @@ public static void install(byte[] bArray, short bOffset, byte bLength) { */ @Override public boolean select() { - KMRepository.instance().onSelect(); + repository.onSelect(); if (keymasterState == KMKeymasterApplet.INSTALL_STATE) { keymasterState = KMKeymasterApplet.FIRST_SELECT_STATE; } else if (keymasterState == KMKeymasterApplet.INACTIVE_STATE) { @@ -127,7 +239,7 @@ public boolean select() { /** De-selects this applet. */ @Override public void deselect() { - KMRepository.instance().onDeselect(); + repository.onDeselect(); if (keymasterState == KMKeymasterApplet.ACTIVE_STATE) { keymasterState = KMKeymasterApplet.INACTIVE_STATE; } @@ -136,7 +248,7 @@ public void deselect() { /** Uninstalls the applet after cleaning the repository. */ @Override public void uninstall() { - KMRepository.instance().onUninstall(); + repository.onUninstall(); if (keymasterState != KMKeymasterApplet.UNINSTALLED_STATE) { keymasterState = KMKeymasterApplet.UNINSTALLED_STATE; } @@ -149,10 +261,7 @@ public void uninstall() { */ @Override public void process(APDU apdu) { - KMRepository.instance().onProcess(); - if (buffer == null) { - buffer = JCSystem.makeTransientByteArray(MAX_LENGTH, JCSystem.CLEAR_ON_RESET); - } + repository.onProcess(); // Verify whether applet is in correct state. if ((keymasterState != KMKeymasterApplet.ACTIVE_STATE) && (keymasterState != KMKeymasterApplet.FIRST_SELECT_STATE)) { @@ -164,11 +273,14 @@ public void process(APDU apdu) { return; } } + // Read the apdu header and buffer. 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)) { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); @@ -176,283 +288,3926 @@ public void process(APDU apdu) { ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); } // Validate whether INS can be supported - if (!(apduIns >= INS_GENERATE_KEY_CMD && apduIns <= INS_PROVISION_CMD)) { + //TODO remove oracle Provisioning Cmd later. + if (!(apduIns >= INS_GENERATE_KEY_CMD && apduIns <= INS_EARLY_BOOT_ENDED_CMD)) { ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } // Validate if INS is provision command if applet is in FIRST_SELECT_STATE. + //TODO remove oracle Provisioning Cmd later. if (keymasterState == KMKeymasterApplet.FIRST_SELECT_STATE) { - if (apduIns != INS_PROVISION_CMD) { + 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 { // Handle the command - handle(apduIns, apdu); - } catch (CardRuntimeException exception) { - if (!(KMException.handle(exception.getReason()))) { - CardRuntimeException.throwIt(exception.getReason()); + 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; + case INS_DEVICE_LOCKED_CMD: + processDeviceLockedCmd(apdu); + break; + case INS_EARLY_BOOT_ENDED_CMD: + processEarlyBootEndedCmd(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (KMException exception) { + freeOperations(); + sendError(apdu, exception.reason); + exception.clear(); + } catch(ISOException exp){ + freeOperations(); + }finally{ + resetData(); + repository.clean(); + } + } + + private void freeOperations(){ + if(data[OP_HANDLE] != KMType.INVALID_VALUE){ + KMOperationState op = repository.findOperation(KMInteger.cast(data[OP_HANDLE]).getShort()); + if(op != null){ + repository.releaseOperation(op); } - } finally { - // Reset the buffer. - Util.arrayFillNonAtomic(buffer, (short) 0, MAX_LENGTH, (byte) 0); } } + private void processEarlyBootEndedCmd(APDU apdu) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + private void processDeviceLockedCmd(APDU apdu) { + receiveIncoming(apdu); + byte[] scratchPad = apdu.getBuffer(); + tmpVariables[0] = KMArray.instance((short) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMInteger.exp()); + tmpVariables[1] = KMVerificationToken.exp(); + KMArray.cast(tmpVariables[0]).add((short) 1, tmpVariables[1]); + // Decode the arguments + tmpVariables[0] = decoder.decode(tmpVariables[0], buffer, bufferStartOffset, bufferLength); + tmpVariables[1] = KMArray.cast(tmpVariables[0]).get((short) 0); + tmpVariables[1] = KMInteger.cast(tmpVariables[1]).getByte(); + data[VERIFICATION_TOKEN] = KMArray.cast(tmpVariables[0]).get((short)1); + validateVerificationToken(data[VERIFICATION_TOKEN],scratchPad); + short verTime = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + short lastDeviceLockedTime = KMInteger.uint_64(repository.deviceLockedTimestamp, (short)0); + if(KMInteger.compare(verTime,lastDeviceLockedTime) > 0){ + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)8, (byte)0); + KMInteger.cast(verTime).getValue(scratchPad,(short)0,(short)8); + repository.deviceLockedFlag = true; + if(tmpVariables[1] == 0x01) repository.deviceUnlockPasswordOnly = true; + else repository.deviceUnlockPasswordOnly = false; + Util.arrayCopy(scratchPad,(short)0,repository.deviceLockedTimestamp,(short)0,(short)repository.deviceLockedTimestamp.length); + } + sendError(apdu,KMError.OK); + } + 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. */ - private void sendOutgoing(byte[] srcBuffer, short srcLength, APDU apdu) { - if (srcLength > MAX_LENGTH) { + public static void sendOutgoing(APDU apdu) { + if (bufferLength > MAX_IO_LENGTH) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } // Send data 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. */ - private short receiveIncoming(byte[] destBuffer, APDU apdu) { - // Initialize source + public static void receiveIncoming(APDU apdu) { byte[] srcBuffer = apdu.getBuffer(); - // Initialize destination - short destOffset = (short) 0; - // Receive data short recvLen = apdu.setIncomingAndReceive(); short srcOffset = apdu.getOffsetCdata(); - short srcLength = apdu.getIncomingLength(); - if (srcLength > MAX_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); } - return srcLength; } - // Commands - private void handle(byte ins, APDU apdu) { - switch (ins) { - 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: - processVerifyAuthenticationCmd(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; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); - } + 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 short processProvisionCmd(APDU apdu) { + private void processAddRngEntropyCmd(APDU apdu) { // Receive the incoming request fully from the master. - short length = receiveIncoming(buffer, apdu); + 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); + //Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) apdu.getBuffer().length, (byte) 0); // 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 - KMArray argsProto = - KMArray.instance((short) 3) - .add((short) 0, keyparams) - .add((short) 1, keyFormat) - .add((short) 2, keyBlob); + 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) { + 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 blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 6); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormat); + KMArray.cast(argsProto).add((short) 2, blob); + KMArray.cast(argsProto).add((short) 3, blob); // Cert - DER encoded issuer + KMArray.cast(argsProto).add((short) 4, blob); // Cert - Expiry Time + KMArray.cast(argsProto).add((short) 5, blob); // Cert - Auth Key Id + // Decode the argument - KMArray args = decoder.decode(argsProto, buffer, (short) 0, length); - // TODO execute the function + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(args).get((short) 2); + // Key format must be RAW format + tmpVariables[0] = KMEnum.cast(tmpVariables[0]).getVal(); + if (tmpVariables[0] != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + data[ORIGIN] = KMType.IMPORTED; + + // get algorithm - only RSA keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.RSA) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST,data[KEY_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short)0); + if(tmpVariables[0] != KMType.SHA2_256) KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + }else{ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get padding - only PKCS1 supported + tmpVariables[0] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING,data[KEY_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short)0); + if(tmpVariables[0] != KMType.RSA_PKCS1_1_5_SIGN) KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + }else{ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE,data[KEY_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short)0); + if(tmpVariables[0] != KMType.ATTEST_KEY) KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + }else{ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE,data[KEY_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + tmpVariables[0] = KMIntegerTag.cast(tmpVariables[0]).getValue(); + if(KMInteger.cast(tmpVariables[0]).getSignificantShort() != 0 || KMInteger.cast(tmpVariables[0]).getShort() != (short)2048){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + }else{ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Import Rsa Key - initializes data[PUB_KEY] and data[SECRET] + importRSAKey(scratchPad); + // persist key + repository.persistAttestationKey(data[PUB_KEY], data[SECRET]); + // save issuer - DER Encoded + tmpVariables[0] = KMArray.cast(args).get((short)3); + repository.setIssuer(KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + // save expiry time - UTC or General Time - YYMMDDhhmmssZ or YYYYMMDDhhmmssZ. + tmpVariables[0] = KMArray.cast(args).get((short)4); + repository.setCertExpiryTime(KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + // Auth Key Id - from cert associated with imported attestation key. + tmpVariables[0] = KMArray.cast(args).get((short)5); + repository.setAuthKeyId(KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + //persist attestation Ids - if any is missing then exception occurs + saveAttId(KMType.ATTESTATION_ID_BRAND); + saveAttId(KMType.ATTESTATION_ID_DEVICE); + saveAttId(KMType.ATTESTATION_ID_PRODUCT); + saveAttId(KMType.ATTESTATION_ID_MANUFACTURER); + saveAttId(KMType.ATTESTATION_ID_MODEL); + saveAttId(KMType.ATTESTATION_ID_IMEI); + saveAttId(KMType.ATTESTATION_ID_MEID); + saveAttId(KMType.ATTESTATION_ID_SERIAL); // Change the state to ACTIVE if (keymasterState == KMKeymasterApplet.FIRST_SELECT_STATE) { - keymasterState = KMKeymasterApplet.ACTIVE_STATE; + provisionDone = true; + if (setBootParamsDone) { + keymasterState = KMKeymasterApplet.ACTIVE_STATE; + } } - // nothing to return - return 0; + sendError(apdu, KMError.OK); } - private void processGetHwInfoCmd(APDU apdu) { - // No arguments expected - // Make the response - KMArray resp = - 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)); - // Reuse the buffer - Util.arrayFillNonAtomic(buffer, (short) 0, MAX_LENGTH, (byte) 0); - // Encode the response - short len = encoder.encode(resp, buffer, (short) 0, MAX_LENGTH); - sendOutgoing(buffer, len, apdu); + private void saveAttId(short attTag){ + tmpVariables[0] = KMKeyParameters.findTag(KMType.BYTES_TAG, attTag,data[KEY_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE){ + tmpVariables[0] = KMByteTag.cast(tmpVariables[0]).getValue(); + repository.persistAttId( + mapToAttId(attTag), + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + }else{ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } } - private short processAddRngEntropyCmd(APDU apdu) { + private byte mapToAttId(short attTag){ + switch (attTag){ + case KMType.ATTESTATION_ID_BRAND: + return repository.ATT_ID_BRAND; + case KMType.ATTESTATION_ID_DEVICE: + return repository.ATT_ID_DEVICE; + case KMType.ATTESTATION_ID_IMEI: + return repository.ATT_ID_IMEI; + case KMType.ATTESTATION_ID_MANUFACTURER: + return repository.ATT_ID_MANUFACTURER; + case KMType.ATTESTATION_ID_MEID: + return repository.ATT_ID_MEID; + case KMType.ATTESTATION_ID_MODEL: + return repository.ATT_ID_MODEL; + case KMType.ATTESTATION_ID_PRODUCT: + return repository.ATT_ID_PRODUCT; + case KMType.ATTESTATION_ID_SERIAL: + return repository.ATT_ID_SERIAL; + } + KMException.throwIt(KMError.INVALID_TAG); + return (byte)0xFF; // should never happen + } + private void processGetKeyCharacteristicsCmd(APDU apdu) { // Receive the incoming request fully from the master. - short length = receiveIncoming(buffer, apdu); + 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 - KMArray argsProto = KMArray.instance((short) 1).add((short) 0, KMByteBlob.instance()); + // 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 HMAC Sharing Parameters + tmpVariables[2] = KMHmacSharingParameters.instance(); + KMHmacSharingParameters.cast(tmpVariables[2]).setNonce( + KMByteBlob.instance(repository.getHmacNonce(), (short) 0, + repository.HMAC_SEED_NONCE_SIZE)); + KMHmacSharingParameters.cast(tmpVariables[2]).setSeed(KMByteBlob.instance((short)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[3], 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 - KMArray args = decoder.decode(argsProto, buffer, (short) 0, length); + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); // Process - KMByteBlob blob = (KMByteBlob) args.get((short) 0); - // Maximum 2KiB of seed is allowed. - if (blob.length() > MAX_SEED_SIZE) { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + 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); } - // Get existing entropy pool. - byte[] entPool = KMUtil.getEntropyPool(); - // Create new temporary pool. - byte[] heapRef = repository.getByteHeapRef(); - short poolStart = repository.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; + // 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] = KMHmacSharingParameters.exp(); + tmpVariables[0] = KMArray.exp(tmpVariables[1]); + tmpVariables[2] = KMArray.instance((short)1); + KMArray.cast(tmpVariables[2]).add((short) 0, tmpVariables[0]); // Vector of hmac params + // Decode the arguments + tmpVariables[2] = decoder.decode(tmpVariables[2], buffer, bufferStartOffset, bufferLength); + 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 - 32 bytes max + tmpVariables[3] = KMHmacSharingParameters.cast(tmpVariables[2]).getSeed(); + tmpVariables[4] = KMByteBlob.cast(tmpVariables[3]).length(); + // if seed is present + if (tmpVariables[4] == repository.HMAC_SEED_NONCE_SIZE) { + // 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 - 32 bytes + tmpVariables[3] = KMHmacSharingParameters.cast(tmpVariables[2]).getNonce(); + tmpVariables[4] = KMByteBlob.cast(tmpVariables[3]).length(); + // if nonce is not present + if (tmpVariables[4] != repository.HMAC_SEED_NONCE_SIZE) { + 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.getSharedKey(), 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){ + //Key Should not be upgraded, but error code should be OK, As per VTS. + //KMException.throwIt(KMError.INVALID_ARGUMENT); + tmpVariables[5] = 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){ + //Key Should not be upgraded, but error code should be OK, As per VTS. + //KMException.throwIt(KMError.INVALID_ARGUMENT); + tmpVariables[5] = 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 + if(tmpVariables[5] != KMError.INVALID_ARGUMENT) { + 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]); + } else { + data[KEY_BLOB] = KMByteBlob.instance((short)0); + } + // 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) 12); + // Arguments + tmpVariables[2] = KMKeyParameters.exp(); + KMArray.cast(tmpVariables[1]).add((short) 0, tmpVariables[2]); // Key Params of wrapped key + 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, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(tmpVariables[1]).add((short) 10, KMInteger.exp()); // Password Sid + KMArray.cast(tmpVariables[1]).add((short) 11, KMInteger.exp()); // Biometric Sid + short i = KMArray.cast(tmpVariables[1]).length(); + // Decode the arguments + short args = decoder.decode(tmpVariables[1], buffer, bufferStartOffset, bufferLength); + // Step -0 - check whether the key format and algorithm supported + // read algorithm + tmpVariables[0] = KMArray.cast(args).get((short) 0); + tmpVariables[1] = KMEnumTag.getValue(KMType.ALGORITHM, tmpVariables[0]); + // read key format + tmpVariables[2] = KMArray.cast(args).get((short) 1); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + // import of RSA and EC not supported with pkcs8 or x509 format + if ((tmpVariables[1] == KMType.RSA || tmpVariables[1] == KMType.EC) && + (tmpVariables[2] != KMType.RAW)) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + // Step -1 parse the wrapping key blob + // read wrapping key blob + data[KEY_BLOB] = KMArray.cast(args).get((short) 6); + // read un wrapping key params + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 8); + // Read App Id and App Data if any from un wrapping key params + 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(tmpVariables[3]).getValue(); } - // Nothing to return - return 0; + if (data[APP_DATA] != KMTag.INVALID_VALUE) { + data[APP_DATA] = KMByteTag.cast(tmpVariables[3]).getValue(); + } + // parse the wrapping key blob + parseEncryptedKeyBlob(scratchPad); + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest SHA2_256. + if(KMEnumTag.getValue(KMType.ALGORITHM,data[HW_PARAMETERS]) != KMType.RSA ){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_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); + } + if(!KMEnumArrayTag.contains(KMType.PURPOSE,KMType.WRAP_KEY, data[HW_PARAMETERS])){ + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + // create rsa decipher + KMCipher cipher = + cryptoProvider.createRsaDecipher( + //KMCipher.PAD_PKCS1, // TODO remove this when KMCipher.PAD_PKCS1_OAEP_SHA256 is supported + 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()); + //read encrypted transport key from args + tmpVariables[0] = KMArray.cast(args).get((short) 5); + // Decrypt the transport key + tmpVariables[1] = + cipher.doFinal( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length(), + scratchPad, + (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[1]); + cryptoProvider.delete(cipher); + // Step 3 - XOR the decrypted AES-GCM key with with masking key + // read masking key + tmpVariables[0] = KMArray.cast(args).get((short) 7); + tmpVariables[1] = KMByteBlob.cast(tmpVariables[0]).length(); + // Length of masking key and transport key must be same. + if (tmpVariables[1] != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + tmpVariables[2] = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (tmpVariables[2] < tmpVariables[1]) { + tmpVariables[3] = (short)(((short)KMByteBlob.cast(tmpVariables[0]).get(tmpVariables[2]))&0x00FF); + tmpVariables[4] = (short)(((short)KMByteBlob.cast(data[SECRET]).get(tmpVariables[2]))&0x00FF); + KMByteBlob.cast(data[SECRET]).add(tmpVariables[2], (byte)(tmpVariables[3]^tmpVariables[4])); + tmpVariables[2]++; + } + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(args).get((short) 2); + data[AUTH_DATA] = KMArray.cast(args).get((short) 9); + data[AUTH_TAG] = KMArray.cast(args).get((short) 3); + data[NONCE] = KMArray.cast(args).get((short) 4); + Util.arrayFillNonAtomic(scratchPad,(short)0, KMByteBlob.cast(data[INPUT_DATA]).length(),(byte)0); + 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[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).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.VERIFICATION_FAILED); + } + cryptoProvider.delete(key); + + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + // create key blob array + data[IMPORTED_KEY_BLOB] = KMArray.instance((short)1); + // add the byte blob containing decrypted input data + KMArray.cast(data[IMPORTED_KEY_BLOB]).add((short)0, + KMByteBlob.instance(scratchPad,(short)0, KMByteBlob.cast(data[INPUT_DATA]).length())); + // encode the key blob + tmpVariables[0] = repository.alloc((short)(KMByteBlob.cast(data[INPUT_DATA]).length()+16)); + tmpVariables[1] = encoder.encode(data[IMPORTED_KEY_BLOB],repository.getHeap(),tmpVariables[0]); + data[IMPORTED_KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), tmpVariables[0], tmpVariables[1]); + importKey(apdu,scratchPad); + } + + private void processAttestKeyCmd(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 keyBlob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 2); + KMArray.cast(argsProto).add((short) 0, keyBlob); + KMArray.cast(argsProto).add((short) 1, keyparams); + // Decode the argument + short args = decoder.decode(argsProto, buffer, bufferStartOffset, bufferLength); + data[KEY_BLOB] = KMArray.cast(args).get((short)0); + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 1); + //parse key blob + parseEncryptedKeyBlob(scratchPad); + // The key which is being attested should be asymmetric i.e. RSA or EC + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]); + if(tmpVariables[0] != KMType.RSA && tmpVariables[0] != KMType.EC){ + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + boolean rsaCert = true; + if(tmpVariables[0] == KMType.EC) rsaCert = false; + // Save attestation application id - must be present. + tmpVariables[0] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID,data[KEY_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + } + // Save attestation challenge + tmpVariables[0] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE,data[KEY_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // extract key characteristics + //makeKeyCharacteristics(scratchPad); + // unique id byte blob - uses application id and temporal month count of creation time. + tmpVariables[0] = makeUniqueId(scratchPad); + // validity period + // active time or creation time - byte blob + // TODO current assumption is that if active and creation time are missing from characteristics then + // then it is an error. Alternative can be to use 1 Jan 1970 as validity start period. + tmpVariables[1] = KMKeyParameters.findTag(KMType.DATE_TAG,KMType.ACTIVE_DATETIME,data[SW_PARAMETERS]); + if(tmpVariables[1] != KMType.INVALID_VALUE) tmpVariables[1] = KMIntegerTag.cast(tmpVariables[1]).getValue(); + else { + tmpVariables[1] = KMKeyParameters.findTag(KMType.DATE_TAG,KMType.CREATION_DATETIME,data[SW_PARAMETERS]); + if(tmpVariables[1] == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_KEY_BLOB); + tmpVariables[1] = KMIntegerTag.cast(tmpVariables[1]).getValue(); + } + // convert milliseconds to UTC date. Start of validity period has to be UTC. + tmpVariables[1] = convertToDate(tmpVariables[1], scratchPad, true); + // expiry time - byte blob + tmpVariables[2] = KMKeyParameters.findTag(KMType.DATE_TAG,KMType.USAGE_EXPIRE_DATETIME,data[SW_PARAMETERS]); + if(tmpVariables[2] != KMType.INVALID_VALUE) { + // compare if the expiry time is greater then 2051 then use generalized time format else use + // utc time format + tmpVariables[2] = KMIntegerTag.cast(tmpVariables[1]).getValue(); + tmpVariables[3] = KMInteger.uint_64(firstJan2051,(short)0); + if(KMInteger.compare(tmpVariables[2],tmpVariables[3])>=0) tmpVariables[2]=convertToDate(tmpVariables[2],scratchPad, false); + else tmpVariables[2] = convertToDate(tmpVariables[1], scratchPad, true); + } else { // if no expiry tag is present then use the attestation key certificate's expiry time + // that was provisioned in the provision command. This will be in Generalized or UTC time + tmpVariables[2] = KMByteBlob.instance(repository.getCertDataBuffer(), + repository.getCertExpiryTime(),repository.getCertExpiryTimeLen()); + } + + // buffer for cert - we allocate 1024 bytes buffer - should be sufficient for 2Kbits RSA cert + tmpVariables[3] = KMByteBlob.instance((short)2048); + // read att application id. + tmpVariables[4] = KMKeyParameters.findTag(KMType.BYTES_TAG,KMType.ATTESTATION_APPLICATION_ID,data[KEY_PARAMETERS]); + if(tmpVariables[4] != KMType.INVALID_VALUE) tmpVariables[4] = KMByteTag.cast(tmpVariables[4]).getValue(); + else tmpVariables[4] = 0; + // read att challenge + tmpVariables[5] = KMKeyParameters.findTag(KMType.BYTES_TAG,KMType.ATTESTATION_CHALLENGE,data[KEY_PARAMETERS]); + if(tmpVariables[5] == KMType.INVALID_VALUE) KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING); + tmpVariables[5] = KMByteTag.cast(tmpVariables[5]).getValue(); + + // create X509 certificate. + KMX509Certificate.encodeCert(tmpVariables[3]/*buf*/, data[KEY_CHARACTERISTICS]/*key char*/, + tmpVariables[0]/*unique Id*/,tmpVariables[1]/*start*/,tmpVariables[2]/*end*/, + data[PUB_KEY]/*pub key/modulus*/,tmpVariables[5],tmpVariables[4], rsaCert); + + // Now sign the cert + // Create signer + Signature signer = cryptoProvider.createRsaSigner( + MessageDigest.ALG_SHA_256, + KMCipher.PAD_PKCS1, + repository.getAttKeyExponent(),(short)0, repository.ATT_KEY_EXP_SIZE, + repository.getAttKeyModulus(),(short)0,repository.ATT_KEY_MOD_SIZE); + //Sign the cert - returns the length of complete cert + tmpVariables[1] = KMX509Certificate.sign(signer); + + // Send the response back. This is slightly different we do not copy the cert blob again. + // We just add CBOR encoding around it. + // Encode the response + buffer = KMX509Certificate.getBuffer(); + // add CBOR header and elements + bufferStartOffset = encoder.encodeCert(KMX509Certificate.getBuffer(), KMX509Certificate.getBufferStart(), + KMX509Certificate.getCertStart(),KMX509Certificate.getCertLength()); + bufferLength = (short)(KMX509Certificate.getCertLength() + (KMX509Certificate.getCertStart()- bufferStartOffset)); + sendOutgoing(apdu); + } + + private short convertToDate(short time, byte[] scratchPad, boolean utcFlag){ + short yrsCount=0; + short monthCount=0; + short dayCount=0; + short hhCount=0; + short mmCount=0; + short ssCount=0; + byte Z = 0x5A; + boolean from2020 = true; + Util.arrayFillNonAtomic(scratchPad,(short)0,(short)256,(byte)0); + Util.arrayCopyNonAtomic( + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad,(short)(8-KMInteger.cast(time).length()),KMInteger.cast(time).length()); + // If the time is less then 1 Jan 2020 then it is an error + if(Util.arrayCompare(scratchPad,(short)0,firstJan2020,(short)0,(short)8) < 0){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if(utcFlag && Util.arrayCompare(scratchPad,(short)0,firstJan2051,(short)0,(short)8) >=0){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if(Util.arrayCompare(scratchPad,(short)0,firstJan2051,(short)0,(short)8) < 0){ + Util.arrayCopyNonAtomic(firstJan2020,(short)0,scratchPad,(short)8, (short)8); + subtract(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + }else{ + from2020 = false; + Util.arrayCopyNonAtomic(firstJan2051,(short)0,scratchPad,(short)8, (short)8); + subtract(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + // divide the given time with four yrs msec count + if(Util.arrayCompare(scratchPad,(short)0,fourYrsMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(fourYrsMsec,(short)0,scratchPad,(short)8, (short)8); + yrsCount = divide(scratchPad,(short)0, (short)8,(short)16); // quotient is multiple of 4 + yrsCount = (short)(yrsCount*4); // number of yrs. + // copy reminder as new dividend + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + // divide the given time with one yr msec count + if(Util.arrayCompare(scratchPad,(short)0,oneYearMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneYearMsec,(short)0, scratchPad,(short)8, (short)8); + yrsCount += divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + // total yrs from 1970 + if(from2020) yrsCount = (short)(2020+yrsCount); + else yrsCount = (short)(2051+yrsCount); + + // divide the given time with one month msec count + if(Util.arrayCompare(scratchPad,(short)0,oneMonthMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneMonthMsec,(short)0, scratchPad,(short)8, (short)8); + monthCount = divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + + // divide the given time with one day msec count + if(Util.arrayCompare(scratchPad,(short)0,oneDayMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneDayMsec,(short)0, scratchPad,(short)8, (short)8); + dayCount = divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + + // divide the given time with one hour msec count + if(Util.arrayCompare(scratchPad,(short)0,oneHourMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneHourMsec,(short)0, scratchPad,(short)8, (short)8); + hhCount = divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + + // divide the given time with one minute msec count + if(Util.arrayCompare(scratchPad,(short)0,oneMinMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneMinMsec,(short)0, scratchPad,(short)8, (short)8); + mmCount = divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + + // divide the given time with one second msec count + if(Util.arrayCompare(scratchPad,(short)0,oneSecMsec,(short)0,(short)8) >=0){ + Util.arrayCopyNonAtomic(oneSecMsec,(short)0, scratchPad,(short)8, (short)8); + ssCount = divide(scratchPad,(short)0, (short)8,(short)16); + Util.arrayCopyNonAtomic(scratchPad,(short)16, scratchPad, (short)0,(short)8); + } + + // Now convert to ascii string YYMMDDhhmmssZ or YYYYMMDDhhmmssZ + Util.arrayFillNonAtomic(scratchPad,(short)0,(short)256,(byte)0); + short len = numberToString(yrsCount, scratchPad,(short)0); // returns YYYY + len += numberToString(monthCount, scratchPad, len); + len += numberToString(dayCount, scratchPad,len); + len += numberToString(hhCount, scratchPad,len); + len += numberToString(mmCount, scratchPad,len); + len += numberToString(ssCount, scratchPad,len); + scratchPad[len] = Z; + len++; + if(utcFlag) return KMByteBlob.instance(scratchPad,(short)2,(short)(len -2)); // YY + else return KMByteBlob.instance(scratchPad,(short)0,len); // YYYY + } + + private short numberToString(short number, byte[] scratchPad, short offset){ + byte zero = 0x30; + byte len = 2; + byte digit = 0; + if(number > 999) len = 4; + byte index = len; + while(index > 0){ + digit = (byte)(number%10); + number = (short)(number / 10); + scratchPad[(short)(offset+index-1)]=(byte)(digit+zero); + index--; + } + return len; + } + + private short makeUniqueId(byte[] scratchPad){ + tmpVariables[0] = KMKeyParameters.findTag(KMType.BOOL_TAG,KMType.INCLUDE_UNIQUE_ID,data[HW_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE){ + return 0; + } + // Concatenate T||C||R + // temporal count T + tmpVariables[0] = KMKeyParameters.findTag(KMType.DATE_TAG,KMType.CREATION_DATETIME,data[SW_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_TAG); + tmpVariables[0] = KMIntegerTag.cast(tmpVariables[0]).getValue(); + tmpVariables[0] = countTemporalCount(tmpVariables[0], scratchPad); // just a short count + Util.setShort(scratchPad,(short)0,tmpVariables[0]); + tmpVariables[1] = (short)2; + + // Application Id C + tmpVariables[0] = KMKeyParameters.findTag(KMType.BYTES_TAG,KMType.ATTESTATION_APPLICATION_ID,data[KEY_PARAMETERS]); + if(tmpVariables[0] == KMType.INVALID_VALUE) KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + tmpVariables[0] = KMByteTag.cast(tmpVariables[0]).getValue(); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + scratchPad,tmpVariables[1], + KMByteBlob.cast(tmpVariables[0]).length() + ); + tmpVariables[1] += KMByteBlob.cast(tmpVariables[0]).length(); + + // Reset After Rotation R - it will be part of HW Enforced key characteristics + scratchPad[tmpVariables[1]] = (byte)0; + tmpVariables[0] = KMKeyParameters.findTag(KMType.BOOL_TAG,KMType.RESET_SINCE_ID_ROTATION, data[HW_PARAMETERS]); + if(tmpVariables[0] != KMType.INVALID_VALUE) { + scratchPad[tmpVariables[1]] = (byte) 0x01; + } + tmpVariables[1]++; + + // Sign - signature becomes unique id of 32 bits. Use 128 bits master key as an hmac key. + HMACKey key = cryptoProvider.createHMACKey(repository.getMasterKeySecret(),(short)0, + (short)repository.getMasterKeySecret().length); + tmpVariables[0] = KMByteBlob.instance((short)32); + tmpVariables[1]=cryptoProvider.hmacSign(key,scratchPad,(short)0,tmpVariables[1], + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff()); + + if(tmpVariables[1] != 32){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return tmpVariables[0]; + } + + private short countTemporalCount(short time, byte[] scratchPad){ + Util.arrayFillNonAtomic(scratchPad,(short)0,(short)24, (byte)0); + short result = 0; + Util.arrayCopyNonAtomic(KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad,(short)(8-KMInteger.cast(time).length()), + KMInteger.cast(time).length()); + Util.arrayCopyNonAtomic(oneMonthMsec,(short)0,scratchPad,(short)8,(short)8); + result = divide(scratchPad, (short)0,(short)8,(short)16); + return result; + } + + private void processDestroyAttIdsCmd(APDU apdu) { + repository.deleteAttIds(); + sendError(apdu, KMError.OK); } - private short processAbortOperationCmd(APDU apdu) { - return 0; + private void processVerifyAuthorizationCmd(APDU apdu) { + sendError(apdu, KMError.UNIMPLEMENTED); } - private short processFinishOperationCmd(APDU apdu) { - return 0; + 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 short processUpdateOperationCmd(APDU apdu) { - return 0; + 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 according to jcard sim + 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.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC) && + 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 short processBeginOperationCmd(APDU apdu) { - return 0; + 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) { + //verify if padding is corrupted. + byte paddingByte = heap[(short)(tmpVariables[1]+len -1)]; + //padding byte always should be <= blocksize + if((short)paddingByte > tmpVariables[0] || + (short)paddingByte <= 0) KMException.throwIt(KMError.INVALID_ARGUMENT); + len = (short)(len - (short)paddingByte); + } + //If padding i.e. pkcs7 then add padding to right + data[OUTPUT_DATA] = KMByteBlob.instance(heap, tmpVariables[1], len); + break; + } } - private short processGetKeyCharacteristicsCmd(APDU apdu) { - return 0; + //update operation should send 0x00 for finish variable, where as finish operation + //should send 0x01 for finish variable. + private void updateAAD(KMOperationState op, byte finish){ + // Is input data absent + if(data[INPUT_DATA] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Update can be called either to update auth data, update input data or both. + // But if it is called for neither then return error. + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + tmpVariables[1] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA,data[KEY_PARAMETERS]); + //For Finish operation the input data can be zero length and associated data can be INVALID_VALUE + //For update operation either input data or associated data should be present. + if(tmpVariables[1] == KMType.INVALID_VALUE && tmpVariables[0] <=0 && finish == 0x00){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Check if associated data is present and update aad still allowed by the operation. + if(tmpVariables[1] != KMType.INVALID_VALUE){ + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + // If allowed the update the aad + tmpVariables[1] = KMByteTag.cast(tmpVariables[1]).getValue(); + op.getCipher().updateAAD( + KMByteBlob.cast(tmpVariables[1]).getBuffer(), + KMByteBlob.cast(tmpVariables[1]).getStartOff(), + KMByteBlob.cast(tmpVariables[1]).length()); + } } + + private void updateAesGcmOperation(KMOperationState op, APDU apdu) { + updateAAD(op, (byte) 0x00); + // Now handle the input data + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + // If the input data is non zero length + if (tmpVariables[0] > 0) { + // input data must be block aligned. + if (tmpVariables[0] % AES_BLOCK_SIZE != 0) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // no more future updateAAD allowed. + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + // Adjust input data wrt to last aes block and saved aes block + tmpVariables[0] = (short) (tmpVariables[0] - AES_BLOCK_SIZE); + if (op.isAesBlockSaved()) { + tmpVariables[0] = (short) (tmpVariables[0] + AES_BLOCK_SIZE); + } + // Allocate new data buffer in which input data will be assembled + tmpVariables[1] = KMByteBlob.instance(tmpVariables[0]); + if (op.isAesBlockSaved() && tmpVariables[0] > 0) { + // First copy the previously saved block to the buffer + Util.arrayCopy( + op.getAesBlock(), + (short) 0, + KMByteBlob.cast(tmpVariables[1]).getBuffer(), + KMByteBlob.cast(tmpVariables[1]).getStartOff(), + AES_BLOCK_SIZE); + tmpVariables[0] = (short)(tmpVariables[0] -AES_BLOCK_SIZE); + if (tmpVariables[0] > 0) { + // Then copy rest of the input data to the buffer + Util.arrayCopy( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(tmpVariables[1]).getBuffer(), + (short) (KMByteBlob.cast(tmpVariables[1]).getStartOff() + AES_BLOCK_SIZE), + tmpVariables[0]); + } + } else { + if (tmpVariables[0] > 0) { + Util.arrayCopy( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(tmpVariables[1]).getBuffer(), + (short) (KMByteBlob.cast(tmpVariables[1]).getStartOff()), + tmpVariables[0]); + } + } + + // Save the last aes block from input data into the op state + tmpVariables[0] = (short) (KMByteBlob.cast(data[INPUT_DATA]).length() - AES_BLOCK_SIZE); + op.setAesBlock( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + (short) (KMByteBlob.cast(data[INPUT_DATA]).getStartOff() + tmpVariables[0]), + AES_BLOCK_SIZE); + data[INPUT_DATA] = tmpVariables[1]; + } - private short processGetHmacSharingParamCmd(APDU apdu) { - return 0; + data[OUTPUT_DATA] = KMByteBlob.instance(KMByteBlob.cast(data[INPUT_DATA]).length()); + // Update the rest of the data + if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) { + // allocate output data buffer as input data is always block aligned. + tmpVariables[0] = + op.getCipher() + .update( + 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()); + //TODO: Not always the output length is equal to input length. + //TODO: Few VTS tests fail because of this below code. + //if (tmpVariables[0] != KMByteBlob.cast(data[INPUT_DATA]).length()) { + // KMException.throwIt(KMError.UNKNOWN_ERROR); + //} + //In case if output length not equal to input length. Allocate actual size of output. + if(tmpVariables[0] != KMByteBlob.cast(data[INPUT_DATA]).length()) { + data[INPUT_DATA] = data[OUTPUT_DATA]; + data[OUTPUT_DATA] = KMByteBlob.instance(tmpVariables[0]); + Util.arrayCopy(KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + tmpVariables[0]); + } + } + // make response + tmpVariables[1] = KMArray.instance((short) 0); + tmpVariables[1] = KMKeyParameters.instance(tmpVariables[1]); + tmpVariables[2] = KMArray.instance((short) 4); + 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 short processVerifyAuthenticationCmd(APDU apdu) { - return 0; + private void finishAesGcmOperation(KMOperationState op, byte[] scratchPad) { + // update aad if there is any and if it is allowed + updateAAD(op, (byte)0x01); + // Check if there at least MAC Length length of data + if(data[INPUT_DATA] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + tmpVariables[1] = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA,data[KEY_PARAMETERS]); + if(!op.isAesBlockSaved() && + (tmpVariables[0] < (short)(op.getMacLength()/8)) && + (op.getPurpose()==KMType.DECRYPT) && + (tmpVariables[1] == KMType.INVALID_VALUE)){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Now add the aes block saved in op.state to input data + if(op.isAesBlockSaved()){ + tmpVariables[0] = KMByteBlob.instance((short)(tmpVariables[0]+AES_BLOCK_SIZE)); + Util.arrayCopy(op.getAesBlock(), (short)0, + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + (short)op.getAesBlock().length); + Util.arrayCopy( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + (short)(KMByteBlob.cast(tmpVariables[0]).getStartOff()+op.getAesBlock().length), + KMByteBlob.cast(data[INPUT_DATA]).length()); + data[INPUT_DATA] = tmpVariables[0]; + } + // Allocate output data buffer based on mac length and encrypt or decrypt operation + tmpVariables[0] = op.getCipher().getAesGcmOutputSize(KMByteBlob.cast(data[INPUT_DATA]).length(), + (short)(op.getMacLength()/8)); + /* if(op.getPurpose() == KMType.ENCRYPT){ + data[OUTPUT_DATA] = KMByteBlob.instance((short)(tmpVariables[0]+(op.getMacLength()/8))); + }else{ + data[OUTPUT_DATA] = KMByteBlob.instance((short)(tmpVariables[0]-(op.getMacLength()/8))); + } + */ + data[OUTPUT_DATA] = KMByteBlob.instance(tmpVariables[0]); + //This will throw KMError.VERIFICATION_FAILED if the tag does not match during decrypt. + tmpVariables[0] = 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()); + if(tmpVariables[0] != KMByteBlob.cast(data[OUTPUT_DATA]).length()){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + + /* //TODO HACK The SunJCE update multipart decryption does not return any output instead it stores + // the input. The doFinal call returns the complete plain text at once. so here as a hack + // we are allocation a 256 buffer(VTS MAX input message size) to store the plain text. + if(KMCipher.SUN_JCE == op.getCipher().getCipherProvider() && op.getPurpose() == KMType.DECRYPT) { + if (KMCipherImpl.aes_gcm_decrypt_final_data != 0x00) { + data[OUTPUT_DATA] = KMCipherImpl.aes_gcm_decrypt_final_data; + KMCipherImpl.aes_gcm_decrypt_final_data = 0x00; + } + } +*/ } - private short processDestroyAttIdsCmd(APDU apdu) { - return 0; + private void beginAesGcmOperation(KMOperationState op) { + short purpose; + // TODO [Venkat] What is the reason to make data[OP_HANDLE] to KMType.INVALID_VALUE + //This is commented because if some exception is thrown below data[OP_HANDLE] points to + // INVALID_VALUE and is not getting cleared from OperationState. + //data[OP_HANDLE] = KMType.INVALID_VALUE; + if (op.getPurpose() == KMType.ENCRYPT) { + purpose = KMCipher.MODE_ENCRYPT; + if (data[IV] == KMType.INVALID_VALUE) { + data[IV] = KMByteBlob.instance((short) 12); + cryptoProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length()); + } + } else { + purpose = KMCipher.MODE_DECRYPT; + } + op.setAesGcmUpdateStart(); + op.setKey(KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + try { + //TODO [Venkat] CryptoException is not been converted to KMException here. + op.setCipher( + cryptoProvider.createAesGcmCipher( + purpose, + op.getMacLength(), + 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())); + } catch (CryptoException exception) { + if(exception.getReason() == CryptoException.ILLEGAL_VALUE) + KMException.throwIt(KMError.INVALID_ARGUMENT); + else if(exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + //data[OP_HANDLE] = op.getHandle(); } - private short processComputeSharedHmacCmd(APDU apdu) { - return 0; + 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 + // However as there is no padding we can treat signing as a RSA decryption operation. + 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 > op.getKeySize()) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + // If Length is same as key size then + // compare the data with key value - data should be less then key value. + if(len == op.getKeySize()) { + 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;*/ + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0,len); + }else if (op.getDigest() == KMType.DIGEST_NONE && op.getPadding() == KMType.RSA_PKCS1_1_5_SIGN) { + /* // If PKCS1 padding and no digest - then 0x01||0x00||PS||0x00 on left such that PS >= 8 bytes + // Data Length should be atleast 11 less then the key size - which is 256 bytes + if(len > (short)(op.getKeySize() - 11)){ + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Fill the scratch pad with pkcs1 padding zero according to RFC 2313 section 8.1 + // Signing is done using private key. + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + scratchPad[0] = 0x00; + scratchPad[1] = 0x01; + //cryptoProvider.newRandomNumber(scratchPad, (short)2, (short)8); + // We fill in 0xFF as PS following the javacard pkcs1 padding example. + tmpVariables[0] = (short)(op.getKeySize()-len-3); + Util.arrayFillNonAtomic(scratchPad,(short)2,tmpVariables[0],(byte)0xFF); + scratchPad[(short)(tmpVariables[0]+2)] = 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)(tmpVariables[0]+3),len); + len = op.getKeySize(); // this will be 256*/ + Util.arrayCopyNonAtomic( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + scratchPad, (short)0,len); + + }else if(op.getDigest()==KMType.SHA2_256 && + (op.getPadding() == KMType.RSA_PKCS1_1_5_SIGN ||op.getPadding() == KMType.RSA_PSS)){ + // Normal case with PKCS1 or PSS padding and with Digest SHA256 + // 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); + }else{ + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + if(op.getPurpose() == KMType.SIGN){ + // len of signature will be 256 bytes + try { + len = op.getSignerVerifier().sign(scratchPad,(short)0,len, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + }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: + //For HMAC, either sign or verify we do sign operation only and we compare the + //signature manually. The reason for doing this is the TAG_MAC_LENGTH can be 32 bytes + //length or less than that in case if it is less than 32 we are truncating it and sending + //back to the user. For Verify user will send the truncated and if we pass the truncated + //signature to javacard verify API it will fail because it expects the full length signature. + Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256, (byte)0); + // digest is always present. + // 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); + // Copy only signature of mac length size. + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad,(short)0, (short) (op.getMacLength() / 8)); + if(op.getPurpose() == KMType.VERIFY){ + if(0 != Util.arrayCompare(KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + (short)(op.getMacLength() / 8))) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + break; + default:// This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } } - private short processDeleteAllKeysCmd(APDU apdu) { - return 0; + 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 short processDeleteKeyCmd(APDU apdu) { - return 0; + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // If one time user Authentication is required + 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()){ // If Auth per operation is required + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + authenticateUser(scratchPad); + } } - private short processUpgradeKeyCmd(APDU apdu) { - return 0; + private void authorizeDeviceUnlock(short hwToken) { + // If device is locked and key characteristics requires unlocked device then check whether + // HW auth token has correct timestamp. + short ptr = + KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]); + if (ptr != KMType.INVALID_VALUE && repository.deviceLockedFlag) { + if (hwToken == KMType.INVALID_VALUE) KMException.throwIt(KMError.DEVICE_LOCKED); + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + // Check if the current auth time stamp is greater then device locked time stamp + if (KMInteger.compare(ptr, KMInteger.uint_64(repository.deviceLockedTimestamp, (short) 0)) + <= 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + // Now check if the device unlock requires password only authentication and whether + // auth token is generated through password authentication or not. + if (repository.deviceUnlockPasswordOnly) { + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + } + // Unlock the device + repository.deviceLockedFlag = false; + Util.arrayFillNonAtomic(repository.deviceLockedTimestamp, (short) 0, (short) 8, (byte) 0); + } } - private short processAttestKeyCmd(APDU apdu) { - return 0; + 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; + } + validateVerificationToken(verToken, scratchPad); + // validate operation handle. + ptr = KMVerificationToken.cast(verToken).getChallenge(); + if(op.getHandle() != KMInteger.cast(ptr).getShort()){ + KMException.throwIt(KMError.VERIFICATION_FAILED); + } } - private short processExportKeyCmd(APDU apdu) { - return 0; + private void validateVerificationToken(short verToken, byte[] scratchPad) { + 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; + } + // concatenation length will be 37 + length of verified parameters list - which is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short params = KMVerificationToken.cast(verToken).getParametersVerified(); + + // 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(); + if (KMByteBlob.cast(ptr).length() != 0) { + len += KMByteBlob.cast(ptr).getValues(scratchPad, (short) 0); + } + // 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 short processImportWrappedKeyCmd(APDU apdu) { - return 0; + 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 + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY ) { + if(data[INPUT_DATA] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + // 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); + } + //TODO refactor and optimize this + if (op.getAlgorithm() == KMType.AES && op.getBlockMode() == KMType.GCM) { + updateAesGcmOperation(op, apdu); + return; + } + if(data[INPUT_DATA] == KMType.INVALID_VALUE){ + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMByteBlob.cast(data[INPUT_DATA]).length(); + 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); + } + } + //Allocate output buffer as input data is already block aligned + data[OUTPUT_DATA] = KMByteBlob.instance(tmpVariables[0]); + // 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(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + // update must fully process all of the input data + if(tmpVariables[0] != KMByteBlob.cast(data[OUTPUT_DATA]).length()){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + // 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 short processImportKeyCmd(APDU apdu) { - return 0; + 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 short processGenerateKey(APDU apdu) { - return 0; + 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]); + op.setKeySize(KMByteBlob.cast(data[SECRET]).length()); + 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. + //As per VTS, for the decryption operation don't send the iv back. + if (data[IV] != KMType.INVALID_VALUE && op.getPurpose() != KMType.DECRYPT) { + tmpVariables[2] = KMArray.instance((short) 1); + if(op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) { + //For AES/DES we are generate an random iv of length 16 bytes. + //While sending the iv back for DES/CBC mode of opeation only send + //8 bytes back. + tmpVariables[1] = KMByteBlob.instance((short) 8); + Util.arrayCopy(KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(tmpVariables[1]).getBuffer(), + KMByteBlob.cast(tmpVariables[1]).getStartOff(), + (short)8); + data[IV] = tmpVariables[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){ + switch(op.getAlgorithm()) { + case KMType.AES: + case KMType.DES: + if(op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + case KMType.EC: + case KMType.HMAC: + if(op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + default: + break; + } + if(!KMEnumArrayTag.contains(KMType.PURPOSE,op.getPurpose(),data[HW_PARAMETERS])){ + KMException.throwIt(KMError.INCOMPATIBLE_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.INCOMPATIBLE_DIGEST); + op.setDigest((byte)param); + } + short paramPadding = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if(paramPadding != KMType.INVALID_VALUE){ + if(KMEnumArrayTag.cast(paramPadding).length() != 1) KMException.throwIt(KMError.INVALID_ARGUMENT); + paramPadding = KMEnumArrayTag.cast(paramPadding).get((short)0); + } + switch(op.getAlgorithm()){ + case KMType.RSA: + if ((paramPadding == KMType.RSA_OAEP || paramPadding == KMType.RSA_PSS) + && param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + case KMType.EC: + case KMType.HMAC: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + 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.INCOMPATIBLE_PADDING_MODE); + } + switch (op.getAlgorithm()){ + case KMType.RSA: + if(param == KMType.INVALID_VALUE) KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + 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.UNSUPPORTED_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.UNSUPPORTED_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.UNSUPPORTED_PADDING_MODE); + 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); + } + if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) { + if(!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])){ + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + } + 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); + } + op.setMacLength(macLen); + } + //TODO Ignore MAC_LENGTH tag for other modes of operation. + //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) { + if(op.getPurpose() == KMType.SIGN) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + } else { + //MAC length may not be specified for verify. + if(op.getPurpose() == KMType.VERIFY) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (macLen < KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } else if (macLen > KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + op.setMacLength(macLen); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private short getMacSize(byte digest){ + switch (digest){ + case KMType.SHA1: + return 160; + case KMType.SHA2_224: + return 224; + case KMType.SHA2_384: + return 384; + case KMType.SHA2_256: + return 256; + case KMType.SHA2_512: + return 512; + case KMType.MD5: + return 128; + default: + return 0; + } + } + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizeAlgorithm(op); + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + authorizeKeyUsageForCount(); + if(!validateHwToken(data[HW_TOKEN],scratchPad)){ + data[HW_TOKEN] = KMType.INVALID_VALUE; + } + authorizeUserSecureIdAuthTimeout(op, scratchPad); + authorizeDeviceUnlock(data[HW_TOKEN]); + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail if it is not a Decrypt operation + 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 && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + } + if (tmpVariables[1] != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(tmpVariables[1]).getValue(); + //For CBC mode and GCM mode if IV is present in key parameters then it must be of right lengths + if (op.getBlockMode() == KMType.CBC && op.getAlgorithm() == KMType.DES && + KMByteBlob.cast(data[IV]).length() != 8) { + KMException.throwIt(KMError.INVALID_NONCE); + } + if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) { + KMException.throwIt(KMError.INVALID_NONCE); + } + if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR) + && op.getAlgorithm() == KMType.AES && + KMByteBlob.cast(data[IV]).length() != 16) { + KMException.throwIt(KMError.INVALID_NONCE); + } + } + if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { + // 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 && op.getDigest() == KMType.SHA2_256){ + padding = KMCipher.PAD_PKCS1_OAEP_SHA256; + } else if (op.getPadding() == KMType.RSA_OAEP && op.getDigest() == KMType.SHA1) { + padding = KMCipher.PAD_PKCS1_OAEP; + } 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; + 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 if (op.getBlockMode() == KMType.ECB) { + alg = KMCipher.ALG_AES_BLOCK_128_ECB_NOPAD; + data[IV] = KMType.INVALID_VALUE; + } else if (op.getBlockMode() == KMType.CTR){ + alg = KMCipher.ALG_AES_CTR; + 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{ + 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); // TODO 8 bytes in length + cryptoProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length() + ); + } + + } else if (op.getBlockMode() == KMType.ECB){ + alg = KMCipher.ALG_DES_ECB_NOPAD; + data[IV] = KMType.INVALID_VALUE; + }else{ + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + } 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: + //For HMAC, either sign or verify we do sign operation only and we compare the + //signature manually. The reason for doing this is the TAG_MAC_LENGTH can be 32 bytes + //length or less than that in case if it is less than 32 we are truncating it and sending + //back to the user. For Verify user will send the truncated and if we pass the truncated + //signature to Javacard verify API it will fail because it expects the full length signature. + 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( + Signature.MODE_SIGN, 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. + if(data[HW_TOKEN] == KMType.INVALID_VALUE) KMException.throwIt(KMError.INVALID_MAC_LENGTH); + authTime = KMIntegerTag.cast(tmpVariables[0]).getValue(); + // authenticate user + authenticateUser(scratchPad); + // set the one time auth + op.setOneTimeAuthReqd(true); + // set the authentication time stamp in operation state + authTime = addIntegers(authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp()); + op.setAuthTime(KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + // auth time validation will happen in update or finish + op.setAuthTimeoutValidated(false); + } else { + // auth per operation required + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + + private void authenticateUser(byte[] 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 boolean 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 false; + } + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (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(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .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(ptr) + .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()); + return verified; +/* + 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) 2); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); // secret + KMArray.cast(tmpVariables[0]).add((short) 1, KMByteBlob.exp()); // public key + 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); + // 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) { + //As per NIST.SP.800-186 page 9, secret for 256 curve should be between + //256-383 + if(((256 <= (short)(KMByteBlob.cast(data[SECRET]).length()*8)) && + (383 >= (short)(KMByteBlob.cast(data[SECRET]).length()*8))) ^ + tmpVariables[2] == 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (tmpVariables[2] != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } 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) { + //As per NIST.SP.800-186 page 9, secret length for 256 curve should be between + //256-383 + if(((256 <= (short)(KMByteBlob.cast(data[SECRET]).length()*8)) && + (383 >= (short)(KMByteBlob.cast(data[SECRET]).length()*8))) ^ + tmpVariables[3] == KMType.P_256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (tmpVariables[3] != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } 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((short)(KMByteBlob.cast(data[SECRET]).length() * 8)); + 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.IMPORT_PARAMETER_MISMATCH); + } + } 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 || tmpVariables[2] != (short)(KMByteBlob.cast(data[SECRET]).length()*8)) { + 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 if(enumVal == KMType.VERIFIED_BOOT) { + repository.selfSignedBootFlag = false; + repository.verifiedBootFlag = true; + }else { + repository.selfSignedBootFlag = false; + repository.verifiedBootFlag = false; + } + 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); + } + /* TODO As per VTS don't do validations for digest, padding, purpose, mode at the time of generation + // 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.UNSUPPORTED_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{ + //TODO as per vts TAG_MIN_MAC_LENGTH if passed can be ignored at creation time. + // 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] == KMTag.INVALID_VALUE)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } else if ((tmpVariables[0] != KMTag.INVALID_VALUE) && (tmpVariables[0] != (short)256)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } else if((tmpVariables[1] != KMType.INVALID_VALUE) && (tmpVariables[1] != KMType.P_256)) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } + + 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]); +/* print("pubkey: ", + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + print("Secret: ", + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + */ + } + + 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) { + //If params does not contain any digest throw unsupported digest error. + if(KMType.INVALID_VALUE == KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + // 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.UNSUPPORTED_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 (((short) (tmpVariables[1] % 8) != 0) || + (tmpVariables[1] < (short) 64)|| + tmpVariables[1] > (short)512) { + 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()); + try { + 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()); + } catch(ISOException e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + 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]); + + if(KMArray.cast(data[KEY_BLOB]).length() == 5) { + tmpVariables[1] = KMArray.instance((short)(tmpVariables[0]+1)); + } else { + tmpVariables[1] = KMArray.instance(tmpVariables[0]); + } + // convert scratch pad to KMArray + 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++; + } + if(KMArray.cast(data[KEY_BLOB]).length() == 5) { + KMArray.cast(tmpVariables[1]).add(index, data[PUB_KEY]); + } + + 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()); + add(scratchPad, buf, (short)(buf+8), (short)(buf+16)); + return KMInteger.uint_64(scratchPad,(short)(buf+16)); + } + + // num1 must be greater then or equal to num2 and both must be positive + private short subtractIntegers(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()); + if(scratchPad[buf] < 0 || scratchPad[(short)(buf+8)] <0)return KMType.INVALID_VALUE; + if(Util.arrayCompare(scratchPad,buf, scratchPad,(short)(buf+8), (short)8) < 1) return KMType.INVALID_VALUE; + subtract(scratchPad,buf,(short)(buf+8),(short)(buf+16)); + return KMInteger.uint_64(scratchPad,(short)(buf+16)); + } + + private void add(byte[] buf, short op1, short op2, short result){ + byte index = 7; + byte carry = 0; + short tmp = 0; + while(index >= 0){ + tmp = (short) (buf[(short)(op1+index)] + buf[(short)(op2+index)]+carry); + carry = 0; + if(tmp > 255) carry = 1; // max unsigned byte value is 255 + buf[(short)(result+index)] = (byte)(tmp & (byte)0xFF); + index--; + } + } + + // subtraction by borrowing. + private void subtract(byte[] buf, short op1, short op2, short result) { +/* short temp1 = KMInteger.uint_64(buf,op1); + short temp2 = KMInteger.uint_64(buf,op2); + //twosComplement(buf, op2); + negate(buf, op2); + add(buf, op1, op2, result); + increment(buf, result); + Util.arrayCopyNonAtomic( + KMInteger.cast(temp1).getBuffer(), KMInteger.cast(temp1).getStartOff(), buf, op1, (short)8); + Util.arrayCopyNonAtomic( + KMInteger.cast(temp2).getBuffer(), KMInteger.cast(temp2).getStartOff(), buf, op2, (short)8); +*/ + byte borrow = 0; + byte index = 7; + short r = 0; + short x = 0; + short y = 0; + while(index >= 0){ + x = (short)(buf[(short)(op1 + index)] & 0xFF); + y = (short)(buf[(short)(op2 + index)] & 0xFF); + r = (short)(x - y - borrow); + borrow = 0; + if(r < 0){ + borrow = 1; + r = (short)(r + 256);// max unsigned byte value is 255 + } + buf[(short)(result + index)] = (byte)(r & 0xFF); + index--; + } + } + /* private void twosComplement(byte[] buf, short op){ + negate(buf, op); + increment(buf, op); + } + private void negate(byte[] buf, short op){ + byte index = 7; + while (index >= 0) { + buf[(short)(op+index)] = (byte) (~buf[(short)(op+index)]); + index--; + } + } + private void increment(byte[] buf, short op){ + byte index = 7; + byte tmp; + byte carry = 1; + while(index <= 0){ + tmp = buf[(short)(op+index)]; + buf[(short)(op+index)] = (byte)(buf[(short)(op+index)] + carry); + if(buf[(short)(op+index)] < tmp) carry = 1; + else carry = 0; + index--; + } + }*/ + // use Euclid's formula: dividend = quotient*divisor + remainder + // i.e. dividend - quotient*divisor = remainder where remainder < divisor. + // so this is division by subtraction until remainder remains. + private short divide(byte [] buf, short dividend, short divisor, short remainder){ + short expCnt = 1; + short q = 0; + // first increase divisor so that it becomes greater then dividend. + while (compare(buf, divisor, dividend) < 0){ + shiftLeft(buf, divisor); + expCnt = (short)(expCnt << 1); + } + // Now subtract divisor from dividend if dividend is greater then divisor. + // Copy remainder in the dividend and repeat. + while(expCnt != 0){ + if(compare(buf, dividend,divisor) >= 0){ + subtract(buf, dividend, divisor, remainder); + copy(buf, remainder, dividend); + q = (short)(q + expCnt); + } + expCnt = (short)(expCnt >> 1); + shiftRight(buf, divisor); + } + //copy(buf, dividend, remainder); + return q; + } + + private void copy(byte[] buf, short from, short to){ + Util.arrayCopyNonAtomic(buf, from, buf,to,(short)8 ); + } + + private byte compare(byte[] buf, short lhs, short rhs){ + return Util.arrayCompare(buf,lhs,buf,rhs,(short)8); + } + private void shiftLeft(byte[] buf, short start){ + byte index = 7; + byte carry = 0; + byte tmp; + while (index >= 0){ + tmp = buf[(short)(start+index)]; + buf[(short)(start+index)] = (byte)(buf[(short)(start+index)] << 1); + buf[(short)(start+index)] = (byte)(buf[(short)(start+index)] + carry); + if(tmp<0) carry = 1; + else carry = 0; + index--; + } + } + + private void shiftRight(byte[] buf, short start) { + byte index = 0; + byte carry = 0; + byte tmp; + while (index < 8) { + tmp = (byte) (buf[(short)(start+index)] & 0x01); + buf[(short)(start+index)] = (byte) (buf[(short)(start+index)] >> 1); + buf[(short)(start+index)] = (byte) (buf[(short)(start+index)] & 0x7F); + buf[(short)(start+index)] = (byte) (buf[(short)(start+index)] | carry); + if (tmp == 1) carry = (byte) 0x80; + else carry = 0; + index++; + } + } +/* + 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/KMOperationState.java b/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java index d1671328..4987b595 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -16,39 +16,225 @@ 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 short purpose; + private short keySize; + private boolean active; + private boolean trustedConfirmation; + private boolean cipherOperation; + // TODO This should be 64 bits + private short handle; + 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; + private boolean aesGcmUpdateAllowed; + + private boolean aesBlockSaved; + private byte[] aesBlock; + private short macLength; - private KMOperationState() { - operationHandle = null; + public KMOperationState(){ + authTime = new byte[8]; + key = new byte[256]; + aesBlock = new byte[KMKeymasterApplet.AES_BLOCK_SIZE]; + reset(); } -/* - public static KMOperationState instance(KMContext context) { - // TODO make operation handle - return context.getRepository().newOperationState(); - } -*/ - public static void create(KMOperationState[] opStateRefTable) { - byte index = 0; - while (index < opStateRefTable.length) { - opStateRefTable[index] = new KMOperationState(); - index++; + + public short getKeySize() { + return keySize; + } + + public void setKeySize(short keySize) { + this.keySize = keySize; + } + + + 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; + aesGcmUpdateAllowed = false; + aesBlockSaved = false; + macLength = 0; + } + //TODO make this random number + public short getOpHandleCounter() { + opHandleCounter++; + if(opHandleCounter < 0){ + opHandleCounter = 0; } + return opHandleCounter; } - public KMInteger getOperationHandle() { - return operationHandle; + public boolean isActive() { + return active; } + public boolean isCipherOperation(){ return cipherOperation;} + public void setCipherOperation(boolean flag){cipherOperation = flag;} - public void setOperationHandle(KMInteger operationHandle) { - this.operationHandle = operationHandle; + public short getHandle() { + return KMInteger.uint_16(handle); } -/* - public void release(KMContext context) { - // TODO release handle - context.getRepository().releaseOperationState(this); + + 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 void setBlockMode(byte blockMode) { + this.blockMode = blockMode; + } + + public byte getDigest() { + return digest; + } + + public void setDigest(byte digest) { + this.digest = digest; + } + + public boolean isAesGcmUpdateAllowed(){ + return aesGcmUpdateAllowed; + } + public void setAesGcmUpdateComplete(){ + aesGcmUpdateAllowed = false; + } + public void setAesGcmUpdateStart(){ + aesGcmUpdateAllowed = true; + } + public byte[] getAesBlock(){ + return aesBlock; + } + + public void setAesBlock(byte[] buf, short start, short length){ + if(aesBlock.length != length) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + Util.arrayCopy(buf,start,aesBlock, (short)0, length); + aesBlockSaved = true; + } + + public boolean isAesBlockSaved() { + return aesBlockSaved; + } + + public void setMacLength(short length) { + macLength = length; + } + + public short getMacLength() { + return macLength; + } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java b/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java index b71aea01..d70c87b7 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -20,454 +20,510 @@ 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 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; + //TODO make the sizes configurable + public static final short INVALID_VALUE = (short) 0x8000; + public static final short HEAP_SIZE = 10000; + public static final short MAX_BLOB_STORAGE = 32; + public static final short AES_GCM_AUTH_TAG_LENGTH = 12; + public static final short MASTER_KEY_SIZE = 16; + public static final short SHARED_SECRET_KEY_SIZE = 16; + public static final short HMAC_SEED_NONCE_SIZE = 32; + public static final short MAX_OPS = 4; + public static final short COMPUTED_HMAC_KEY_SIZE = 32; + public static final short ATT_ID_HEAP_SIZE = 160; + public static final short CERT_DATA_MEM_SIZE = 256; + // Key Attestation related constants + public static final byte ATT_ID_TABLE_SIZE = 8; + public static final byte ATT_ID_HEADER_SIZE = 3; + public static final short ATT_KEY_MOD_SIZE = 256; + public static final short ATT_KEY_EXP_SIZE = 256; + public static final byte ATT_ID_OFFSET = 0x02; + public static final byte ATT_ID_LENGTH = 0x01; + public static final byte ATT_ID_TAG = 0x00; + public static final byte ATT_ID_BRAND = 0x00; + public static final byte ATT_ID_DEVICE = 0x01; + public static final byte ATT_ID_PRODUCT = 0x02; + public static final byte ATT_ID_SERIAL = 0x03; + public static final byte ATT_ID_IMEI = 0x04; + public static final byte ATT_ID_MEID = 0x05; + public static final byte ATT_ID_MANUFACTURER = 0x06; + public static final byte ATT_ID_MODEL = 0x07; + final short[] attIdTags ={ + KMType.ATTESTATION_ID_BRAND, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_SERIAL, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MODEL}; + public byte[] attestCertIssuer; + public byte[] attestCertAuthKeyId; + // 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 AESKey masterKey = null; - 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 boolean deviceUnlockPasswordOnly; + private byte[] masterKey; + private byte[] sharedKey; + private byte[] computedHmacKey; + private byte[] hmacNonce; + // Volatile memory heap + private byte[] heap; + private short heapIndex; + + //Attesation Id Table; + private Object[] attIdTable; + + // Operation State Table + private Object[] operationStateTable; + + // boot parameters + //TODO change the following into private + 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 byte[] deviceLockedTimestamp; + // attestation + private byte[] attKeyModulus; + private byte[] attKeyExponent; + private byte[] attIdMem; + private short attIdMemIndex; + // attestation cert data + private byte[] certData; + private short issuer; + private short issuerLen; + private short certExpiryTime; + private short certExpiryTimeLen; + private short authKeyId; + private short authKeyIdLen; + private short certDataIndex; + private boolean attIdSupported; public static KMRepository instance() { - if (repository == null) { - repository = new KMRepository(); - } return repository; } - public KMRepository(){ - initialize(); - } - - private void initialize() { - // Initialize buffers and context. - JCSystem.beginTransaction(); - encoder = new KMEncoder(); - decoder = new KMDecoder(); - operationStateTable = new KMOperationState[4]; - // 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]; + public KMRepository() { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + heapIndex = 0; + attIdMem = new byte[ATT_ID_HEAP_SIZE]; + attIdMemIndex = 0; + 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++; } - JCSystem.commitTransaction(); + 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++; + } + deviceLockedFlag = false; + deviceLockedTimestamp = new byte[8]; + deviceUnlockPasswordOnly = false; + Util.arrayFillNonAtomic(deviceLockedTimestamp,(short)0,(short)8,(byte)0); + + attKeyModulus = new byte[ATT_KEY_MOD_SIZE]; + attKeyExponent = new byte[ATT_KEY_EXP_SIZE]; + attIdTable = new Object[ATT_ID_TABLE_SIZE]; + attIdSupported = false; + index = 0; + while(index < ATT_ID_TABLE_SIZE){ + attIdTable[index] = new short[ATT_ID_HEADER_SIZE]; + ((short[])attIdTable[index])[ATT_ID_TAG] = attIdTags[index]; + ((short[])attIdTable[index])[ATT_ID_LENGTH] = 0; + index++; + } + certData = new byte[CERT_DATA_MEM_SIZE]; + certDataIndex = 0; + repository = this; } - public KMEncoder getEncoder() { - return encoder; + public KMOperationState reserveOperation(){ + short index = 0; + while(index < MAX_OPS){ + if(!((KMOperationState)operationStateTable[index]).isActive()){ + ((KMOperationState)operationStateTable[index]).activate(); + return (KMOperationState)operationStateTable[index]; + } + index++; + } + return null; } - - public KMDecoder getDecoder() { - return decoder; + public void releaseOperation(KMOperationState op){ + op.reset(); + } + public void initMasterKey(byte[] key, short len) { + if (masterKey == null) { + masterKey = new byte[MASTER_KEY_SIZE]; + if(len != MASTER_KEY_SIZE) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + Util.arrayCopy(key, (short) 0, masterKey, (short) 0, len); + } } - public void onUninstall() { - masterKey = null; + public void initHmacSharedSecretKey(byte[] key, short len) { + if (sharedKey == null) { + sharedKey = new byte[SHARED_SECRET_KEY_SIZE]; + } + if(len != SHARED_SECRET_KEY_SIZE) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + Util.arrayCopy(key, (short) 0, sharedKey, (short) 0, len); } - public void onProcess() { - reset(); + + public void initComputedHmac(byte[] key, short start, short len) { + if (computedHmacKey == null) { + computedHmacKey = new byte[COMPUTED_HMAC_KEY_SIZE]; + } + if(len != COMPUTED_HMAC_KEY_SIZE) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + Util.arrayCopy(key, (short) 0, computedHmacKey, start, len); } - private void reset() { - Util.arrayFillNonAtomic(byteHeap, (short) 0, (short) byteHeap.length, (byte) 0); - byteHeapIndex = 0; - short index = 0; - while (index < typeRefTable.length) { - typeRefTable[index] = null; - index++; + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (hmacNonce == null) { + hmacNonce = new byte[HMAC_SEED_NONCE_SIZE]; } - 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++; + if (len != HMAC_SEED_NONCE_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } - 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++; + Util.arrayCopy(nonce, (short) 0, hmacNonce, (short) 0, len); + } + /* TODO according to hal specs seed should always be empty. + Confirm this before removing the code as it is also specified that keymasterdevice with storage + must store and return the seed. + public void initHmacSeed(byte[] seed, short len) { + if (hmacSeed == null) { + hmacSeed = new byte[HMAC_SEED_NONCE_SIZE]; } + if(len != HMAC_SEED_NONCE_SIZE) KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + Util.arrayCopy(seed, (short) 0, hmacSeed, (short) 0, len); } - public void onDeselect() { - // TODO clear operation state? +*/ + public void onUninstall() { + // TODO change this + Util.arrayFillNonAtomic(masterKey, (short) 0, (short) masterKey.length, (byte) 0); } - public void onSelect() { - // Nothing to be done currently. + public void onProcess() {} + + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, heapIndex, (byte) 0); + heapIndex = 0; } - public AESKey getMasterKey() { + public void onDeselect() {} + + public void onSelect() {} + + public byte[] getMasterKeySecret() { return masterKey; } - // 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 - ISOException.throwIt(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 - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - byte[] ret = (byte[]) uint64Array[uint64Index]; - uint64Index++; - return ret; - } else { - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + public short alloc(short length) { + if (((short) (heapIndex + length)) > heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - return null;// this will never be executed. + heapIndex += length; + return (short) (heapIndex - length); } - public KMByteBlob newByteBlob() { - if (blobRefIndex >= byteBlobRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMByteBlob ret = byteBlobRefTable[blobRefIndex]; - blobRefIndex++; - return ret; + public byte[] getHeap() { + return heap; } - public KMInteger newInteger() { - if (intRefIndex >= integerRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMInteger ret = integerRefTable[intRefIndex]; - intRefIndex++; - return ret; + public byte[] getSharedKey() { + return sharedKey; } - public KMEnumTag newEnumTag() { - if (enumTagRefIndex >= enumTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMEnumTag ret = enumTagRefTable[enumTagRefIndex]; - enumTagRefIndex++; - return ret; + public byte[] getHmacNonce() { + return hmacNonce; } - public KMEnumArrayTag newEnumArrayTag() { - if (enumArrayTagRefIndex >= enumArrayTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMEnumArrayTag ret = enumArrayTagRefTable[enumArrayTagRefIndex]; - enumArrayTagRefIndex++; - return ret; + public void setHmacNonce(byte[] hmacNonce) { + Util.arrayCopy(hmacNonce, (short) 0, this.hmacNonce, (short) 0, HMAC_SEED_NONCE_SIZE); + } + public byte[] getComputedHmacKey() { + return computedHmacKey; } - public KMIntegerTag newIntegerTag() { - if (intTagRefIndex >= intTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMIntegerTag ret = intTagRefTable[intTagRefIndex]; - intTagRefIndex++; - return ret; + public void setComputedHmacKey(byte[] computedHmacKey) { + Util.arrayCopy( computedHmacKey, (short) 0, this.computedHmacKey, (short) 0, COMPUTED_HMAC_KEY_SIZE); } - public KMIntegerArrayTag newIntegerArrayTag() { - if (intArrayTagRefIndex >= intArrayTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(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++; } - KMIntegerArrayTag ret = intArrayTagRefTable[intArrayTagRefIndex]; - intArrayTagRefIndex++; - return ret; } - public KMBoolTag newBoolTag() { - if (boolTagRefIndex >= boolTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public boolean validateAuthTag(short authTag) { + KMAuthTag tag = findTag(authTag); + if(tag != null){ + return true; } - KMBoolTag ret = boolTagRefTable[boolTagRefIndex]; - boolTagRefIndex++; - return ret; + return false; } - public KMByteTag newByteTag() { - if (byteTagRefIndex >= byteTagRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void removeAuthTag(short authTag) { + KMAuthTag tag = findTag(authTag); + if(tag == null){ + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } - KMByteTag ret = byteTagRefTable[byteTagRefIndex]; - byteTagRefIndex++; - return ret; + JCSystem.beginTransaction(); + tag.reserved = false; + short index = 0; + while(index < AES_GCM_AUTH_TAG_LENGTH){ + tag.authTag[index] = 0; + index++; + } + tag.usageCount = 0; + keyBlobCount--; + JCSystem.commitTransaction(); } - public KMKeyParameters newKeyParameters() { - if (keyParametersRefIndex >= keyParametersRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(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++; } - KMKeyParameters ret = keyParametersRefTable[keyParametersRefIndex]; - keyParametersRefIndex++; - return ret; + keyBlobCount = 0; + JCSystem.commitTransaction(); } - public KMArray newArray() { - if (arrayRefIndex >= arrayRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(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++; } - KMArray ret = arrayRefTable[arrayRefIndex]; - arrayRefIndex++; - return ret; + return null; } - public KMKeyCharacteristics newKeyCharacteristics() { - if (keyCharRefIndex >= keyCharRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public short getRateLimitedKeyCount(short authTag) { + KMAuthTag tag = findTag(authTag); + if (tag != null) { + return tag.usageCount; } - KMKeyCharacteristics ret = keyCharRefTable[keyCharRefIndex]; - keyCharRefIndex++; - return ret; + return KMType.INVALID_VALUE; } - public KMHardwareAuthToken newHwAuthToken() { - if (hwAuthTokenRefIndex >= hwAuthTokenRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void setRateLimitedKeyCount(short authTag, short val) { + KMAuthTag tag = findTag(authTag); + JCSystem.beginTransaction(); + if (tag != null) { + tag.usageCount = val; } - KMHardwareAuthToken ret = hwAuthTokenRefTable[hwAuthTokenRefIndex]; - hwAuthTokenRefIndex++; - return ret; + JCSystem.commitTransaction(); } - public KMHmacSharingParameters newHmacSharingParameters() { - if (hmacSharingParamsRefIndex >= hmacSharingParamsRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(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++; } - KMHmacSharingParameters ret = hmacSharingParamsRefTable[hmacSharingParamsRefIndex]; - hmacSharingParamsRefIndex++; - return ret; + return null; } - public KMVerificationToken newVerificationToken() { - if (verTokenRefIndex >= verTokenRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMVerificationToken ret = verTokenRefTable[verTokenRefIndex]; - verTokenRefIndex++; - return ret; + public void persistAttestationKey(short mod, short exp) { + JCSystem.beginTransaction(); + if(KMByteBlob.cast(mod).length() != ATT_KEY_MOD_SIZE) KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + Util.arrayCopy( + KMByteBlob.cast(mod).getBuffer(), + KMByteBlob.cast(mod).getStartOff(), + attKeyModulus, + (short)0, + KMByteBlob.cast(mod).length()); + + if(KMByteBlob.cast(exp).length() != ATT_KEY_EXP_SIZE) KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + Util.arrayCopy( + KMByteBlob.cast(exp).getBuffer(), + KMByteBlob.cast(exp).getStartOff(), + attKeyExponent, + (short)0, + KMByteBlob.cast(exp).length()); + JCSystem.commitTransaction(); } - public KMOperationState newOperationState() { - if (opStateRefIndex >= opStateRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - KMOperationState ret = operationStateTable[opStateRefIndex]; - opStateRefIndex++; - return ret; + public byte[] getAttKeyModulus() { + return attKeyModulus; } - public void releaseOperationState(KMOperationState state){ - opStateRefIndex--; - if(opStateRefIndex <0){ - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - opStateRefTable[opStateRefIndex] = state; + public byte[] getAttKeyExponent() { + return attKeyExponent; + } + + public void persistAttId(byte id, byte[] buf, short start, short len){ + JCSystem.beginTransaction(); + short[] attId = (short[])attIdTable[id]; + attId[ATT_ID_OFFSET] = allocAttIdMemory(len); + attId[ATT_ID_LENGTH] = len; + Util.arrayCopy(buf,start, attIdMem,attId[ATT_ID_OFFSET],len); + attIdSupported = true; + JCSystem.commitTransaction(); + } + + public short getAttId(byte id, byte[] buf, short start){ + short[] attId = (short[])attIdTable[id]; + Util.arrayCopy(attIdMem,attId[ATT_ID_OFFSET],buf,start,attId[ATT_ID_LENGTH]); + return attId[ATT_ID_LENGTH]; + } + + public short getAttIdOffset(byte id){ + short[] attId = (short[])attIdTable[id]; + return attId[ATT_ID_OFFSET]; + } + + public byte[] getAttIdBuffer(byte id){ + return attIdMem; + } + + public short getAttIdLen(byte id){ + short[] attId = (short[])attIdTable[id]; + return attId[ATT_ID_LENGTH]; + } + public short getAttIdTag(byte id){ + short[] attId = (short[])attIdTable[id]; + return attId[ATT_ID_TAG]; } - public KMVector newVector() { - if (vectorRefIndex >= vectorRefTable.length) { - // TODO this is placeholder exception value. + + private short allocAttIdMemory(short len){ + if (((short) (attIdMemIndex + len)) > attIdMem.length) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - KMVector ret = vectorRefTable[vectorRefIndex]; - vectorRefIndex++; - return ret; + attIdMemIndex += len; + return (short) (attIdMemIndex - len); } - public KMEnum newEnum() { - if (enumRefIndex >= enumRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + public void deleteAttIds(){ + JCSystem.beginTransaction(); + Util.arrayFillNonAtomic(attIdMem,(short)0,(short)attIdMem.length,(byte)0); + short index = 0; + while(index < ATT_ID_TABLE_SIZE){ + short[] attId = (short[])attIdTable[index]; + attId[ATT_ID_OFFSET] = 0; + attId[ATT_ID_LENGTH] = 0; + index++; } - KMEnum ret = enumRefTable[enumRefIndex]; - enumRefIndex++; - return ret; + attIdSupported = false; + JCSystem.commitTransaction(); + } + public boolean isAttIdSupported(){ + return attIdSupported; + } + public short getIssuer() { + return issuer; } - public KMType[] getTypeArrayRef(){ - return typeRefTable; + public void setIssuer(byte[] buf, short start, short len) { + this.issuer = allocCertData(len); + this.issuerLen = len; + Util.arrayCopy(buf,start,certData, issuer,len); } - public byte[] getByteHeapRef(){ - return byteHeap; + public short getIssuerLen() { + return issuerLen; } - public short newTypeArray(short length) { - if (((short) (typeRefIndex + length)) >= typeRefTable.length) { - // TODO this is placeholder exception value. - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - typeRefIndex += length; - return (short) (typeRefIndex - length); + public short getCertExpiryTime() { + return certExpiryTime; + } + + public void setCertExpiryTime(byte[] buf, short start, short len) { + this.certExpiryTime = allocCertData(len); + this.certExpiryTimeLen = len; + Util.arrayCopy(buf,start,certData, certExpiryTime,len); + } + + public short getCertExpiryTimeLen() { + return certExpiryTimeLen; } - public short newByteArray(short length) { - if (((short) (byteHeapIndex + length)) >= byteHeap.length) { - // TODO this is placeholder exception value. + + public short getAuthKeyId() { + return authKeyId; + } + + public void setAuthKeyId(byte[] buf, short start, short len) { + this.authKeyId = allocCertData(len); + this.authKeyIdLen = len; + Util.arrayCopy(buf,start,certData,authKeyId,len); + } + + public short getAuthKeyIdLen() { + return authKeyIdLen; + } + public byte[] getCertDataBuffer(){ + return certData; + } + private short allocCertData(short len){ + if (((short) (certDataIndex + len)) > certData.length) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - byteHeapIndex += length; - return (short) (byteHeapIndex - length); + certDataIndex += len; + return (short) (certDataIndex - len); } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java b/Applet/Applet/src/com/android/javacard/keymaster/KMRng.java similarity index 77% rename from Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java rename to Applet/Applet/src/com/android/javacard/keymaster/KMRng.java index 0d1080df..a2e79901 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMUtil.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMRng.java @@ -2,6 +2,7 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.AESKey; import javacard.security.CryptoException; @@ -9,24 +10,21 @@ import javacard.security.RandomData; import javacardx.crypto.Cipher; -public class KMUtil { - private static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys - public static final byte AES_BLOCK_SIZE = 16; +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 AESKey aesKey; private static Cipher aesCbc; private static byte[] entropyPool; public static void init() { - entropyPool = new byte[ENTROPY_POOL_SIZE]; - counter = KMRepository.instance().newIntegerArray((short) 8); - KMUtil.initEntropyPool(entropyPool); + entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); + counter = JCSystem.makeTransientByteArray((short)8, JCSystem.CLEAR_ON_RESET); + KMRng.initEntropyPool(entropyPool); try { - //Note: ALG_AES_BLOCK_128_CBC_NOPAD not supported by simulator. aesCbc = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); } catch (CryptoException exp) { - // TODO change this to proper error code - ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } aesKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); } @@ -58,10 +56,10 @@ public static void initEntropyPool(byte[] pool) { // 8 byte counter and 16 byte block size. public static void newRandomNumber(byte[] num, short startOff, short length) { KMRepository repository = KMRepository.instance(); - byte[] bufPtr = repository.getByteHeapRef(); - short countBufInd = repository.newByteArray(AES_BLOCK_SIZE); - short randBufInd = repository.newByteArray(AES_BLOCK_SIZE); - short len = AES_BLOCK_SIZE; + 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) { @@ -71,7 +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. - aesCbc.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); @@ -94,9 +92,7 @@ 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; } } } diff --git a/Applet/Applet/src/com/android/javacard/keymaster/KMSEProvider.java b/Applet/Applet/src/com/android/javacard/keymaster/KMSEProvider.java new file mode 100644 index 00000000..5ef2517f --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMSEProvider.java @@ -0,0 +1,153 @@ +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 KMSEProvider { + KeyPair createRsaKeyPair(); + KeyPair createECKeyPair(); + ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength); + RSAPrivateKey createRsaKey(byte[] modBuffer, short modOff, short modLength, + byte[] privBuffer, short privOff, short privLength); + + HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength); + DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength); + AESKey createAESKey(short keysize); + AESKey createAESKey(byte[] buf, short startOff, short length); + DESKey createTDESKey(); + HMACKey createHMACKey(short keysize); + short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff); + + + // Oneshot Operations + void newRandomNumber(byte[] num, short offset, short length); + void addRngEntropy(byte[] num, short offset, short length); + byte[] getTrueRandomNumber(short len); + + 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); + + short aesCCMSign( + byte[] bufIn, + short bufInStart, + short buffInLength, + byte[] masterKeySecret, + byte[] bufOut, + short bufStart); + + HMACKey cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength); + + short cmacKdf(byte[] keyMaterial, byte[] label, byte[] context, short contextStart, short contextLength, byte[] keyBuf, short keyStart); + + 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); + short hmacSign(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart); + boolean hmacVerify(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, + byte[] mac, short macStart, short macLength); + + // Persistent Operations + short initSymmetricOperation(byte purpose, byte alg, byte digest, byte padding, byte blockMode, + byte[] keyBuf, short keyStart, short keyLength); + short initSymmetricOperation(byte purpose, byte alg, byte digest, + byte[] keyBuf, short keyStart, short keyLength); + short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, + byte[] privKeyBuf, short privKeyStart, short privKeyLength, + byte[] modBuf, short modStart, short modLength); + short initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, + byte[] privKeyBuf, short privKeyStart, short privKeyLength); + short updateOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, + byte[] outputDataBuf, short outputDataStart); + short finishOperation(short opHandle, byte[] inputDataBuf, short inputDataStart, short inputDataLength, + byte[] outputDataBuf, short outputDataStart); + void abortOperation(short opHandle); + + short hmacInit(byte[] keyBuf, short keyStart, short keyLength, byte digest, byte mode); + short hmacSign(short opHandle, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart); + boolean hmacVerify(short opHandle, byte[] keyBuf, short keyStart, short keyLength, + byte[] data, short dataStart, short dataLength, + byte[] mac, short macStart, short macLength); + short hmacUpdate(short opHandle, byte[] dataBuf, short dataStart, short dataLength); + + + 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 createAesGcmCipher(short mode, short tagLen, 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); + + short getSystemTimeInMilliSeconds(byte[] timeBuf, short timeStart, short timeOffset); + short addListener(KMEventListener listener, byte eventType); + short getEventData(byte[] eventBuf, short eventStart, short eventLength); + + //Capability query - should return true + boolean isAlgSupported(byte alg); + boolean isKeySizeSupported(byte alg, short keySize); + boolean isCurveSupported(byte eccurve); + boolean isDigestSupported(byte alg, byte digest); + boolean isPaddingSupported(byte alg, byte padding); + boolean isBlockModeSupported(byte alg, byte blockMode); + + //Capability query - may return true + boolean isSystemTimerSupported(); + boolean isBootEventSupported(); +} 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..a42d5f8c 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMType.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMType.java @@ -16,8 +16,30 @@ 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; + + //Event Type + public static final byte BOOT_EVENT = 0; + + // 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; + public static final byte X509_CERT = 0x0B; + // 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 +51,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 +79,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 +113,40 @@ 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 VERIFIED_BOOT = 0x00; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte UNVERIFIED_BOOT = 0x02; + public static final byte FAILED_BOOT = 0x03; + + // 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 CTR = 0x03; + public static final byte GCM = 0x20; // Digest public static final short DIGEST = 0x0005; @@ -156,7 +200,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 +269,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/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 d47c80e2..c1c6d23c 100644 --- a/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -18,6 +18,7 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; +import javacard.framework.Util; public class KMVerificationToken extends KMType { public static final byte CHALLENGE = 0x00; @@ -25,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) { - ISOException.throwIt(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/KMX509Certificate.java b/Applet/Applet/src/com/android/javacard/keymaster/KMX509Certificate.java new file mode 100644 index 00000000..cb5c99a2 --- /dev/null +++ b/Applet/Applet/src/com/android/javacard/keymaster/KMX509Certificate.java @@ -0,0 +1,785 @@ +package com.android.javacard.keymaster; + +import javacard.framework.Util; +import javacard.security.Signature; + +// The class encodes strongbox generated amd signed attestation certificate. This only encodes +// required fields of the certificates. It is not meant to be generic X509 cert encoder. +// Whatever fields that are fixed are added as byte arrays. The Extensions are encoded as per +// the values. +// The certificate is assembled with leafs first and then the sequences. + +public class KMX509Certificate { + // DER encoded object identifiers required by the cert. + // sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + private static final byte[] sha256WithRSAEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x0B + }; + // countryName - 2.5.4.6 + private static final byte[] country = {0x06, 0x03, 0x55, 0x04, 0x06}; + // stateOrProvinceName - 2.5.4.8 + private static final byte[] stateName = {0x06, 0x03, 0x55, 0x04, 0x08}; + // organizationName - 2.5.4.10 + private static final byte[] orgName = {0x06, 0x03, 0x55, 0x04, 0x0A}; + // organizationalUnitName - 2.5.4.11 + private static final byte[] orgUnitName = {0x06, 0x03, 0x55, 0x04, 0x0B}; + // commonName - 2.5.4.3 + private static final byte[] commonName = {0x06, 0x03, 0x55, 0x04, 0x03}; + // rsaEncryption - 1.2.840.113549.1.1.1 + private static final byte[] rsaEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + // ecPublicKey - 1.2.840.10045.2.1 + private static final byte[] eccPubKey = {0x06,0x07,0x2A,(byte)0x86,0x48,(byte)0xCE,0x3D,0x02,0x01}; + // prime256v1 curve - 1.2.840.10045.3.1.7 + private static final byte[] prime256v1 = {0x06,0x08,0x2A,(byte)0x86,0x48,(byte)0xCE,0x3D,0x03,0x01,0x07}; + // Key Usage Extn - 2.5.29.15 + private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + // Android Extn - 1.3.6.1.4.1.11129.2.1.17 + private static final byte[] androidExtn = { + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + // Authority Key Identifier Extn - 2.5.29.35 + private static final byte[] authKeyIdExtn = {0x06, 0x03, 0X55, 0X1D, 0X23}; + + // Fixed field values - DER encoded + // Version with value 2 and EXPLICIT id 0 with INTEGER type- DER encoded + private static final byte[] X509Version = {(byte) 0XA0, 0x03, 0x02, 0x01, 0x02}; + // Serial Number with value 1 + private static final byte[] X509SerialNum = {0x02, 0x01, 0x01}; + // Signature algorithm identifier - always sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509SignAlgIdentifier = { + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 + }; + // Issuer field is not fixed but it will be given in the provision command in DER Encoded form + // i.e. subject of the cert for attesting key. Following is the placeholder example sequence of + // 5 elements: C=US, ST=California, O=Google, Inc.,OU=Android, CN=Android Software Attestation Key + // TODO move the following to test + private static final byte[] X509Issuer = { + 0x30, 0x76, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0C, 0x0A, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, + 0x72, 0x6E, 0x69, 0x61, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0C, 0x47, + 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x31, 0x10, 0x30, 0x0E, 0x06, + 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x07, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x31, 0x29, 0x30, + 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x20, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x20, + 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x4B, 0x65, 0x79 + }; + // Validity is not fixed field + // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys + private static final byte[] X509Subject = { + 0x30, 0x1F, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x14, 0x41, 0x6e, 0x64, + 0x72, 0x6f, 0x69, 0x64, 0x20, 0x4B, 0x65, 0x79, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x4B, 0x65, + 0x79 + }; + // Subject Public Key Info is not a fixed field. However the for rsa public key or ec dsa + // public key the algorithm identifier is always the same. + // rsaEncryption - 1.2.840.113549.1.1.1 followed by NULL parameters + private static final byte[] subPubKeyRsaAlgId = { + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 + }; + + private static final byte keyUsageSign = (byte) 0x80; // 0 bit + private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit + private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit + + private static final byte KEYMASTER_VERSION = 4; + private static final byte ATTESTATION_VERSION = 3; + private static final byte[] pubExponent = {0x01, 0x00, 0x01}; + private static final byte SERIAL_NUM = (byte) 0x01; + private static final byte X509_VERSION = (byte) 0x02; + + private static short certStart; + private static short signatureOffset; + private static short tbsOffset; + private static short tbsLength; + private static short attChallenge; + private static short attAppId; + private static short hwParams; + private static short swParams; + private static short notBefore; + private static short notAfter; + private static short pubKey; + private static short uniqueId; + + private static short stackPtr; + private static byte[] stack; + private static short start; + private static short length; + private static KMRepository repo; + + private static void init() { + if (repo == null) repo = KMRepository.instance(); + stack = null; + stackPtr = 0; + certStart = 0; + signatureOffset = 0; + start = 0; + length = 0; + attChallenge = 0; + attAppId = 0; + hwParams = 0; + swParams = 0; + notBefore = 0; + notAfter = 0; + pubKey = 0; + uniqueId = 0; + tbsLength = 0; + } + + public static void encodeCert( + short buf, + short keyChar, + short uniqueId, + short notBefore, + short notAfter, + short pubKey, + short attChallenge, + short attAppId, + boolean rsaCert) { + init(); + stack = KMByteBlob.cast(buf).getBuffer(); + start = KMByteBlob.cast(buf).getStartOff(); + length = KMByteBlob.cast(buf).length(); + stackPtr = (short) (start + length); + KMX509Certificate.attChallenge = attChallenge; + KMX509Certificate.attAppId = attAppId; + KMX509Certificate.hwParams = KMKeyCharacteristics.cast(keyChar).getHardwareEnforced(); + KMX509Certificate.swParams = KMKeyCharacteristics.cast(keyChar).getSoftwareEnforced(); + KMX509Certificate.notBefore = notBefore; + KMX509Certificate.notAfter = notAfter; + KMX509Certificate.pubKey = pubKey; + KMX509Certificate.uniqueId = uniqueId; + short last = stackPtr; + decrementStackPtr((short)256); + signatureOffset = stackPtr; + pushBitStringHeader((byte) 0, (short)(last - stackPtr)); + //signatureOffset = pushSignature(null, (short) 0, (short) 256); + pushAlgorithmId(X509SignAlgIdentifier); + tbsLength = stackPtr; + pushTbsCert(rsaCert); + tbsOffset = stackPtr; + tbsLength = (short)(tbsLength - tbsOffset); + pushSequenceHeader((short)(last-stackPtr)); + //print(stack, stackPtr, (short)(last - stackPtr)); + certStart = stackPtr; + } + + private static void pushTbsCert(boolean rsaCert) { + short last = stackPtr; + pushExtensions(); + // subject public key info + if (rsaCert) { + pushRsaSubjectKeyInfo(); + }else{ + pushEccSubjectKeyInfo(); + } + // subject + pushBytes(X509Subject, (short) 0, (short) X509Subject.length); + pushValidity(); + // issuer - der encoded + pushBytes(repo.getCertDataBuffer(), repo.getIssuer(), repo.getIssuerLen()); + // Algorithm Id + pushAlgorithmId(X509SignAlgIdentifier); + // Serial Number + pushByte(SERIAL_NUM); + pushIntegerHeader((short) 1); + // Version + pushByte(X509_VERSION); + pushIntegerHeader((short) 1); + pushByte((byte) 0x03); + pushByte((byte) 0xA0); + // Finally sequence header. + pushSequenceHeader((short) (last - stackPtr)); + } + private static void pushExtensions(){ + short last = stackPtr; + byte keyusage = 0; + byte unusedBits = 8; + pushAuthKeyId(); + if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, hwParams)) { + keyusage = (byte) (keyusage | keyUsageSign); + unusedBits = 7; + } + if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, hwParams)) { + keyusage = (byte) (keyusage | keyUsageKeyEncipher); + unusedBits = 5; + } + if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.DECRYPT, hwParams)) { + keyusage = (byte) (keyusage | keyUsageDataEncipher); + unusedBits = 4; + } + if (keyusage != 0) pushKeyUsage(keyusage, unusedBits); + pushKeyDescription(); + pushSequenceHeader((short) (last - stackPtr)); + // Extensions have explicit tag of [3] + pushLength((short)(last-stackPtr)); + pushByte((byte) 0xA3); + } + + // Time SEQUENCE{UTCTime, UTC or Generalized Time) + private static void pushValidity(){ + short last = stackPtr; if (notAfter != 0) { + pushBytes( + KMByteBlob.cast(notAfter).getBuffer(), + KMByteBlob.cast(notAfter).getStartOff(), + KMByteBlob.cast(notAfter).length()); + } else { + pushBytes(repo.getCertDataBuffer(), repo.getCertExpiryTime(), repo.getCertExpiryTimeLen()); + } + pushTimeHeader(KMByteBlob.cast(notAfter).length()); + pushBytes( + KMByteBlob.cast(notBefore).getBuffer(), + KMByteBlob.cast(notBefore).getStartOff(), + KMByteBlob.cast(notBefore).length()); + pushTimeHeader(KMByteBlob.cast(notBefore).length()); + pushSequenceHeader((short)(last-stackPtr)); + } + + private static void pushTimeHeader(short len) { + if (len == 13) { // UTC Time + pushLength((short) 0x0D); + pushByte((byte) 0x17); + } else if (len == 15) { // Generalized Time + pushLength((short) 0x0F); + pushByte((byte) 0x18); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public exponent + // as positive integer} + private static void pushRsaSubjectKeyInfo() { + short last = stackPtr; + pushBytes(pubExponent, (short) 0, (short) pubExponent.length); + pushIntegerHeader((short) pubExponent.length); + pushBytes( + KMByteBlob.cast(pubKey).getBuffer(), + KMByteBlob.cast(pubKey).getStartOff(), + KMByteBlob.cast(pubKey).length()); + // encode modulus as positive if the MSB is 1. + if (KMByteBlob.cast(pubKey).get((short) 0) < 0){ + pushByte((byte) 0x00); + pushIntegerHeader((short)(KMByteBlob.cast(pubKey).length()+1)); + } else { + pushIntegerHeader(KMByteBlob.cast(pubKey).length()); + } + pushSequenceHeader((short) (last - stackPtr)); + pushBitStringHeader((byte) 0x00, (short) (last - stackPtr)); + pushRsaEncryption(); + pushSequenceHeader((short) (last - stackPtr)); + } + // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} + private static void pushEccSubjectKeyInfo() { + short last = stackPtr; + pushBytes( + KMByteBlob.cast(pubKey).getBuffer(), + KMByteBlob.cast(pubKey).getStartOff(), + KMByteBlob.cast(pubKey).length()); + pushBitStringHeader((byte)0x00,KMByteBlob.cast(pubKey).length()); + pushEcDsa(); + pushSequenceHeader((short) (last - stackPtr)); + } + private static void pushEcDsa(){ + short last = stackPtr; + pushBytes(prime256v1,(short)0,(short)prime256v1.length); + pushBytes(eccPubKey,(short)0,(short)eccPubKey.length); + pushSequenceHeader((short)(last - stackPtr)); + } + private static void pushRsaEncryption(){ + short last = stackPtr; + pushNullHeader(); + pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); + pushSequenceHeader((short) (last - stackPtr)); + } + // KeyDescription ::= SEQUENCE { + // attestationVersion INTEGER, # Value 3 + // attestationSecurityLevel SecurityLevel, # See below + // keymasterVersion INTEGER, # Value 4 + // keymasterSecurityLevel SecurityLevel, # See below + // attestationChallenge OCTET_STRING, # Tag::ATTESTATION_CHALLENGE from attestParams + // uniqueId OCTET_STRING, # Empty unless key has Tag::INCLUDE_UNIQUE_ID + // softwareEnforced AuthorizationList, # See below + // hardwareEnforced AuthorizationList, # See below + // } + private static void pushKeyDescription() { + short last = stackPtr; + pushHWParams(); + pushSWParams(); + if (uniqueId != 0) { + pushOctetString( + KMByteBlob.cast(uniqueId).getBuffer(), + KMByteBlob.cast(uniqueId).getStartOff(), + KMByteBlob.cast(uniqueId).length()); + }else{ + pushOctetStringHeader((short)0); + } + + pushOctetString( + KMByteBlob.cast(attChallenge).getBuffer(), + KMByteBlob.cast(attChallenge).getStartOff(), + KMByteBlob.cast(attChallenge).length()); + pushEnumerated(KMType.STRONGBOX); + pushByte(KEYMASTER_VERSION); + pushIntegerHeader((short) 1); + pushEnumerated(KMType.STRONGBOX); + pushByte(ATTESTATION_VERSION); + pushIntegerHeader((short) 1); + pushSequenceHeader((short) (last - stackPtr)); + pushOctetStringHeader((short) (last - stackPtr)); + pushBytes(androidExtn, (short) 0, (short) androidExtn.length); + pushSequenceHeader((short)(last-stackPtr)); + } + + private static void pushSWParams() { + short last = stackPtr; + //ATTESTATION_APPLICATION_ID 709 is softwareenforced. + short[] tagIds = {709, 706,705,704,703,702,701,601,600,509,508,507,506,505, + 504, 503, 402,401,400,303,200,10,6,5,3,2,1 + }; + byte index = 0; + do { + if(tagIds[index] == KMType.ATTESTATION_APPLICATION_ID) { + pushAttIds(tagIds[index]); + continue; + } + pushParams(swParams, tagIds[index]); + }while(++index < tagIds.length); + pushSequenceHeader((short) (last - stackPtr)); + } + + private static void pushHWParams() { + short last = stackPtr; + //Attestation ids are not included. As per VTS attestation ids are not supported currenlty. + short[] tagIds = {706,705,704,703,702,701,601,600,509,508,507,506,505, + 504, 503, 402,401,400,303,200,10,6,5,3,2,1 + }; + byte index = 0; + do { + if(pushAttIds(tagIds[index])) continue; + if(tagIds[index] == KMType.ROOT_OF_TRUST) { + pushRoT(); + continue; + } + if(pushParams(hwParams,tagIds[index])) continue; + } while (++index < tagIds.length); + pushSequenceHeader((short) (last - stackPtr)); + } + + private static boolean pushParams(short params, short tagId) { + short index = 0; + short arr = KMKeyParameters.cast(params).getVals(); + short len = KMArray.cast(arr).length(); + while (index < len) { + short tag = KMArray.cast(arr).get(index); + if(tagId == KMTag.getKey(tag)) { + pushTag(tag); + return true; + } + index++; + } + return false; + } + + private static void pushTag(short tag) { + short type = KMTag.getTagType(tag); + short tagId = KMTag.getKey(tag); + short val; + switch (type) { + case KMType.BYTES_TAG: + val = KMByteTag.cast(tag).getValue(); + pushBytesTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.ENUM_TAG: + val = KMEnumTag.cast(tag).getValue(); + pushEnumTag(tagId, (byte) val); + break; + case KMType.ENUM_ARRAY_TAG: + val = KMEnumArrayTag.cast(tag).getValues(); + pushEnumArrayTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + val = KMIntegerTag.cast(tag).getValue(); + pushIntegerTag( + tagId, + KMInteger.cast(val).getBuffer(), + KMInteger.cast(val).getStartOff(), + KMInteger.cast(val).length()); + break; + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + // TODO According to keymaster hal only one user secure id is used but this conflicts with + // tag type which is ULONG-REP. Currently this is encoded as SET OF INTEGERS + val = KMIntegerArrayTag.cast(tag).getValues(); + pushIntegerArrayTag(tagId, val); + break; + case KMType.BOOL_TAG: + val = KMBoolTag.cast(tag).getVal(); + pushBoolTag(tagId); + break; + default: + KMException.throwIt(KMError.INVALID_TAG); + break; + } + } + // RootOfTrust ::= SEQUENCE { + // verifiedBootKey OCTET_STRING, + // deviceLocked BOOLEAN, + // verifiedBootState VerifiedBootState, + // verifiedBootHash OCTET_STRING, + // } + // VerifiedBootState ::= ENUMERATED { + // Verified (0), + // SelfSigned (1), + // Unverified (2), + // Failed (3), + // } + private static void pushRoT() { + short last = stackPtr; + byte val = 0x00; + // verified boot hash + pushOctetString(repo.verifiedBootHash, (short) 0, (short) repo.verifiedBootHash.length); + // verified boot state + // TODO change this once verifiedBootState is supported in repo + if (repo.selfSignedBootFlag) val = KMType.SELF_SIGNED_BOOT; + else if (repo.verifiedBootFlag) val = KMType.VERIFIED_BOOT; + else val = KMType.UNVERIFIED_BOOT; + pushEnumerated(val); + // device locked + val = 0x00; + if (repo.deviceLockedFlag) val = (byte) 0xFF; + pushBoolean(val); + // verified boot Key + pushOctetString(repo.verifiedBootKey, (short) 0, (short) repo.verifiedBootKey.length); + // Finally sequence header + pushSequenceHeader((short) (last - stackPtr)); + // ... and tag Id + pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - stackPtr)); + } + + private static void pushOctetString(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + pushOctetStringHeader(len); + } + + private static void pushBoolean(byte val) { + pushByte(val); + pushBooleanHeader((short) 1); + } + + private static void pushBooleanHeader(short len) { + pushLength(len); + pushByte((byte) 0x01); + } + // All Attestation Id tags are byte tags/octet strings + private static boolean pushAttIds(short tagId) { + if (attAppId != 0 && KMType.ATTESTATION_APPLICATION_ID == tagId) { + pushBytesTag( + KMType.ATTESTATION_APPLICATION_ID, + KMByteBlob.cast(attAppId).getBuffer(), + KMByteBlob.cast(attAppId).getStartOff(), + KMByteBlob.cast(attAppId).length()); + return true; + } + if(!repo.isAttIdSupported()) return true; + byte index = 0; + while (index < repo.ATT_ID_TABLE_SIZE) { + if (repo.getAttIdLen(index) != 0) { + if(tagId == repo.getAttIdTag(index)) { + pushBytesTag( + repo.getAttIdTag(index), + repo.getAttIdBuffer(index), + repo.getAttIdOffset(index), + repo.getAttIdLen(index)); + return true; + } + } + index++; + } + return false; + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short len) { + short last = stackPtr; + short index = 0; + while (index < len) { + pushByte(buf[(short) (start + index)]); + pushIntegerHeader((short) 1); + index++; + } + pushSetHeader((short) (last - stackPtr)); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushIntegerArrayTag(short tagId, short arr) { + short last = stackPtr; + short index = 0; + short len = KMArray.cast(arr).length(); + short ptr; + while (index < len) { + ptr = KMArray.cast(arr).get(index); + pushInteger( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + index++; + } + pushSetHeader((short) (last - stackPtr)); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + + private static void pushSetHeader(short len) { + pushLength(len); + pushByte((byte) 0x31); + } + + private static void pushEnumerated(byte val) { + short last = stackPtr; + pushByte(val); + pushEnumeratedHeader((short) (last - stackPtr)); + } + + private static void pushEnumeratedHeader(short len) { + pushLength(len); + pushByte((byte) 0x0A); + } + + private static void pushBoolTag(short tagId) { + short last = stackPtr; + pushNullHeader(); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + + private static void pushNullHeader() { + pushByte((byte) 0); + pushByte((byte) 0x05); + } + + private static void pushEnumTag(short tagId, byte val) { + short last = stackPtr; + pushByte(val); + pushIntegerHeader((short) (last - stackPtr)); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + + private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { + short last = stackPtr; + pushInteger(buf, start, len); +// pushIntegerHeader((short) (last - stackPtr)); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + // Ignore leading zeros. Only Unsigned Integers are required hence if MSB is set then add 0x00 + // as most significant byte. + private static void pushInteger(byte[] buf, short start, short len) { + short last = stackPtr; + byte index = 0; + while (index < (byte) len) { + if (buf[(short) (start + index)] != 0) break; + index++; + } + if (index == (byte) len) { + pushByte((byte) 0x00); + } else { + pushBytes(buf, (short) (start + index), (short) (len - index)); + if (buf[(short) (start + index)] < 0) { // MSB is 1 + pushByte((byte) 0x00); // always unsigned int + } + } + pushIntegerHeader((short) (last - stackPtr)); + } + // Bytes Tag is a octet string and tag id is added explicitly + private static void pushBytesTag(short tagId, byte[] buf, short start, short len) { + short last = stackPtr; + pushBytes(buf, start, len); + pushOctetStringHeader((short) (last - stackPtr)); + pushTagIdHeader(tagId, (short) (last - stackPtr)); + } + + // tag id <= 30 ---> 0xA0 | {tagId} + // 30 < tagId < 128 ---> 0xBF 0x{tagId} + // tagId >= 128 ---> 0xBF 0x80+(tagId/128) 0x{tagId - (128*(tagId/128))} + private static void pushTagIdHeader(short tagId, short len) { + pushLength(len); + short count = (short) (tagId / 128); + if (count > 0) { + pushByte((byte) (tagId - (128 * count))); + pushByte((byte) (0x80 + count)); + pushByte((byte) 0xBF); + } else if (tagId > 30) { + pushByte((byte) tagId); + pushByte((byte) 0xBF); + } else { + pushByte((byte) (0xA0 | (byte) tagId)); + } + } + // SEQUENCE {ObjId, OCTET STRING{BIT STRING{keyUsage}}} + private static void pushKeyUsage(byte keyUsage, byte unusedBits) { + short last = stackPtr; + pushByte(keyUsage); + pushBitStringHeader(unusedBits, (short) (last - stackPtr)); + pushOctetStringHeader((short) (last - stackPtr)); + pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); + pushSequenceHeader((short) (last - stackPtr)); + } + // SEQUENCE {ObjId, OCTET STRING{SEQUENCE{[0]keyIdentifier}}} + private static void pushAuthKeyId() { + short last = stackPtr; + if (repo.getAuthKeyId() == 0) return; + + pushKeyIdentifier( + repo.getCertDataBuffer(), + repo.getAuthKeyId(), + repo.getAuthKeyIdLen()); // key identifier is [0]'th tagged in a sequence + pushSequenceHeader((short) (last - stackPtr)); + pushOctetStringHeader((short) (last - stackPtr)); + pushBytes(authKeyIdExtn, (short) 0, (short) authKeyIdExtn.length); // ObjId + pushSequenceHeader((short) (last - stackPtr)); + } + + private static void pushKeyIdentifier(byte[] buf, short start, short len) { + pushBytes(buf, start, len); // keyIdentifier + pushLength(len); // len + pushByte((byte) 0x80); // Context specific tag [0] + } + + private static void pushAlgorithmId(byte[] algId) { + pushBytes(algId, (short) 0, (short) algId.length); + } + + private static short pushSignature(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + short signatureOff = stackPtr; + pushBitStringHeader((byte) 0, len); + return signatureOff; + } + + private static void pushIntegerHeader(short len) { + pushLength(len); + pushByte((byte) 0x02); + } + + private static void pushOctetStringHeader(short len) { + pushLength(len); + pushByte((byte) 0x04); + } + + private static void pushSequenceHeader(short len) { + pushLength(len); + pushByte((byte) 0x30); + } + + private static void pushBitStringHeader(byte unusedBits, short len) { + pushByte(unusedBits); + pushLength((short) (len + 1)); // 1 extra byte for unused bits byte + pushByte((byte) 0x03); + } + + private static void pushLength(short len) { + if(len< 128){ + pushByte((byte) len); + } else if (len < 256) { + pushByte((byte) len); + pushByte((byte) 0x81); + } else { + pushShort(len); + pushByte((byte) 0x82); + } + } + + private static void pushShort(short val) { + decrementStackPtr((short) 2); + Util.setShort(stack, stackPtr, val); + } + + private static void pushByte(byte val) { + decrementStackPtr((short) 1); + stack[stackPtr] = val; + } + + private static void pushBytes(byte[] buf, short start, short len) { + decrementStackPtr(len); + if (buf != null) { + Util.arrayCopyNonAtomic(buf, start, stack, stackPtr, len); + } + } + + private static void decrementStackPtr(short cnt) { + stackPtr = (short) (stackPtr - cnt); + if (start > stackPtr) KMException.throwIt(KMError.UNKNOWN_ERROR); + } + + public static short sign(Signature signer) { + short ret = signer.sign(stack,tbsOffset,tbsLength,stack,signatureOffset); + //print(getBuffer(),getCertStart(),getCertLength()); + return ret; + } + public static short getCertStart(){ + return certStart; + } + public static short getCertEnd(){ + return (short)(start +length - 1); + } + public static short getCertLength(){ + return (short)(getCertEnd() - getCertStart() + 1); + } + public static short getBufferStart(){ + return start; + } + public static short getBufferLength(){ + return length; + } + public static byte[] getBuffer(){ + return stack; + } + + /* private static void print(byte[] buf, short start, short length){ + StringBuilder sb = new StringBuilder(); + for(int i = start; i < (start+length); i++){ + sb.append(String.format("%02X", buf[i])) ; + //if((i-start)%16 == 0 && (i-start) != 0) sb.append(String.format("\n")); + } + System.out.println(sb.toString()); + }*/ +} diff --git a/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java b/Applet/Applet/test/com/android/javacard/test/KMFrameworkTest.java index 15e61109..4ace527b 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.KMSEProvider; +import com.android.javacard.keymaster.KMSEProviderImpl; 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,61 @@ 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 javacard.security.RSAPublicKey; 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 static final byte[] X509Issuer = { + 0x30, 0x76, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0C, 0x0A, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, + 0x72, 0x6E, 0x69, 0x61, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0C, 0x47, + 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x31, 0x10, 0x30, 0x0E, 0x06, + 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x07, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x31, 0x29, 0x30, + 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x20, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x20, + 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x4B, 0x65, 0x79 + }; + // AttestationApplicationId ::= SEQUENCE { + // * packageInfoRecords SET OF PackageInfoRecord, + // * signatureDigests SET OF OCTET_STRING, + // * } + // * + // * PackageInfoRecord ::= SEQUENCE { + // * packageName OCTET_STRING, + // * version INTEGER, + // * } + private static final byte[] attAppId = {0x30, 0x10, 0x31, 0x0B, 0x30, 0x04, 0x05, 'A', 'B', 'C', + 'D', 'E', 0x02, 0x01, 0x01, 0x31, 0x02, 0x04, 0x00}; + private static final byte[] attChallenge = {'c','h','a','l','l','e','n','g','e'}; + private static final byte[] expiryTime = {0x32,0x30,0x35,0x37,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A}; + private static final byte[] authKeyId = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2}; + + + + + private short status; + private short keyCharacteristics; + private short keyBlob; + private KMSEProvider sim; @Test public void test_Lifecycle_Success() { // Create simulator + //KMJcardSimulator.jcardSim = true; + sim = KMSEProviderImpl.instance(); + sim.bypassAesGcm(); CardSimulator simulator = new CardSimulator(); // Install applet @@ -49,24 +100,448 @@ 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()); + */ + KMSEProvider cryptoProvider = KMSEProviderImpl.instance(); + KMEncoder encoder = new KMEncoder(); + KeyPair rsaKeyPair = cryptoProvider.createRsaKeyPair(); + byte[] pub = new byte[4]; + short len = ((RSAPublicKey)rsaKeyPair.getPublic()).getExponent(pub,(short)1); + byte[] priv = new byte[256]; + byte[] mod = new byte[256]; + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getModulus(mod,(short)0); + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getExponent(priv,(short)0); + short arrPtr = KMArray.instance((short)15); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short rsaPubExpTag = KMIntegerTag.instance(KMType.ULONG_TAG,KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pub, (short)0)); + short byteBlob1 = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob1).add((short)0, KMType.RSA_PKCS1_1_5_SIGN); + short padding = KMEnumArrayTag.instance(KMType.PADDING, byteBlob1); + short byteBlob2 = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob2).add((short)0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob2); + KMArray.cast(arrPtr).add((short)0, boolTag); + KMArray.cast(arrPtr).add((short)1, keySize); + KMArray.cast(arrPtr).add((short)2, digest); + KMArray.cast(arrPtr).add((short)3, rsaPubExpTag); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + KMArray.cast(arrPtr).add((short)5, padding); + KMArray.cast(arrPtr).add((short)6, purpose); + byte[] buf = "Attestation Id".getBytes(); + //Attestatation Ids. + KMArray.cast(arrPtr).add((short)7, KMByteTag.instance(KMType.ATTESTATION_ID_BRAND, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)8, KMByteTag.instance(KMType.ATTESTATION_ID_PRODUCT, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)9, KMByteTag.instance(KMType.ATTESTATION_ID_DEVICE, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)10, KMByteTag.instance(KMType.ATTESTATION_ID_MODEL, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)11, KMByteTag.instance(KMType.ATTESTATION_ID_IMEI, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)12, KMByteTag.instance(KMType.ATTESTATION_ID_MEID, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)13, KMByteTag.instance(KMType.ATTESTATION_ID_MANUFACTURER, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)14, KMByteTag.instance(KMType.ATTESTATION_ID_SERIAL, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW);// Note: VTS uses PKCS8 + short keyBlob = KMArray.instance((short)2); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(priv,(short)0,(short)256)); + KMArray.cast(keyBlob).add((short)1, KMByteBlob.instance(mod,(short)0,(short)256)); + byte[] blob = new byte[620]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)6); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + short byteBlob3 = KMByteBlob.instance(X509Issuer, (short)0, (short)X509Issuer.length); + arg.add((short)3, byteBlob3); + short byteBlob4 = KMByteBlob.instance(expiryTime, (short)0, (short)expiryTime.length); + arg.add((short)4, byteBlob4); + short byteBlob5 = KMByteBlob.instance(authKeyId, (short)0, (short)authKeyId.length); + arg.add((short)5, byteBlob5); + CommandAPDU apdu = encodeApdu((byte)0x23, arrPtr); + //print(apdu.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + } + private CommandAPDU encodeApdu(byte ins, short cmd){ + byte[] buf = new byte[2048]; + buf[0] = (byte)0x80; + buf[1] = ins; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + KMEncoder encoder = new KMEncoder(); + short len = encoder.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, len); + byte[] apdu = new byte[7+len]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+len)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + return new CommandAPDU(apdu); + } + + + 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){ @@ -74,18 +549,19 @@ public void testGetHwInfoCmd(CardSimulator simulator){ //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 +569,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 +584,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; i0)){ +/* if(((i-1)%38 == 0) && ((i-1) >0)){ sb.append(";\n"); } - } +*/ } System.out.println(sb.toString()); + System.out.println(cmdApdu.length); } - private KMArray makeProvisionCmd() { + private short makeProvisionCmd() { // Argument 1 - KMArray vals = - KMArray.instance((short) 1) - .add((short) 0, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); - KMKeyParameters keyparams = KMKeyParameters.instance(vals); + short arrPtr = KMArray.instance((short) 1); + KMArray vals = KMArray.cast(arrPtr); + vals.add((short) 0, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + short keyparamsPtr = KMKeyParameters.instance(arrPtr); // Argument 2 - KMEnum keyFormat = KMEnum.instance(KMType.KEY_FORMAT, KMType.X509); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.X509); // Argument 3 byte[] byteBlob = new byte[48]; for (short i = 0; i < 48; i++) { byteBlob[i] = (byte) i; } - KMByteBlob keyBlob = KMByteBlob.instance(byteBlob, (short) 0, (short)byteBlob.length); + short keyBlobPtr = KMByteBlob.instance(byteBlob, (short) 0, (short)byteBlob.length); // Array of expected arguments - return KMArray.instance((short) 3) - .add((short) 0, keyparams) - .add((short) 1, keyFormat) - .add((short) 2, keyBlob); + short argPtr = KMArray.instance((short) 3); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyparamsPtr); + arg.add((short) 1, keyFormatPtr); + arg.add((short) 2, keyBlobPtr); + return argPtr; } - private KMArray makeAddRngEntropyCmd() { + + private short makeGenerateKeyCmd(byte alg, short keysize) { + // Argument + short arrPtr = KMArray.instance((short) 4); + KMArray vals = KMArray.cast(arrPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] pubVal = {0x00, 0x01, 0x00, 0x01}; + //byte[] digest = {KMType.SHA1, KMType.SHA2_256}; + byte[] digest = {KMType.DIGEST_NONE}; + byte[] padding = {KMType.PADDING_NONE}; + vals.add((short)0, KMEnumTag.instance(KMType.ALGORITHM, alg)); + vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16(keysize))); + //vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.USERID, KMInteger.uint_32(intVal, (short)0))); + //vals.add((short)2, KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(val, (short)0, (short)val.length))); + vals.add((short)2, KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pubVal,(short)0))); + vals.add((short)3, KMEnumArrayTag.instance(KMType.DIGEST, KMByteBlob.instance(digest,(short)0, (short)digest.length))); + //vals.add((short)5, KMEnumArrayTag.instance(KMType.PADDING, KMByteBlob.instance(padding,(short)0, (short)padding.length))); + short keyParamsPtr = KMKeyParameters.instance(arrPtr); + // Array of expected arguments + short argPtr = KMArray.instance((short) 1); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyParamsPtr); + return argPtr; + } + private short makeImportKeySymmCmd(short alg, short size) { + // Argument 1 + short arrPtr; + byte digestType; + if(alg == KMType.HMAC) { + arrPtr = KMArray.instance((short) 6); + digestType = KMType.SHA2_256; + } else{ + arrPtr = KMArray.instance((short) 5); + digestType = KMType.DIGEST_NONE; + } + KMArray vals = KMArray.cast(arrPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] pubVal = {0x00, 0x01, 0x00, 0x01}; + byte[] digest = new byte[1]; + digest[0] = digestType; + vals.add((short)0, KMEnumTag.instance(KMType.ALGORITHM, (byte)alg)); + vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.USERID, KMInteger.uint_32(intVal, (short)0))); + vals.add((short)2, KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(val, (short)0, (short)val.length))); + vals.add((short)3, KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16(size))); + vals.add((short)4, KMEnumArrayTag.instance(KMType.DIGEST, KMByteBlob.instance(digest,(short)0, (short)digest.length))); + if (alg == KMType.HMAC) { + vals.add( + (short) 5,KMIntegerTag.instance(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMInteger.uint_16(size))); + } + short keyParamsPtr = KMKeyParameters.instance(arrPtr); + // Argument 2 + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + short len = 0; + byte[] secret = new byte[32]; + Key key; + // Argument 3 + switch (alg){ + case KMType.AES: + key = sim.createAESKey(size); + len = ((AESKey)key).getKey(secret, (short)0); + break; + case KMType.DES: + key = sim.createTDESKey(); + size = 168; + len = ((DESKey)key).getKey(secret, (short)0); + break; + case KMType.HMAC: + key = sim.createHMACKey(size); + len = ((HMACKey)key).getKey(secret, (short)0); + break; + default: + return 0; + } + short keyBlob = KMArray.instance((short)1); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(secret,(short)0,len)); + KMEncoder encoder = new KMEncoder(); + byte[] blob = new byte[256]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + // Array of expected arguments + short argPtr = KMArray.instance((short) 3); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyParamsPtr); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + return argPtr; + } + + private short makeImportKeyRsaCmd() { + // Argument 1 + short arrPtr = KMArray.instance((short) 5); + KMArray vals = KMArray.cast(arrPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] pubVal = {0x00, 0x01, 0x00, 0x01}; + byte[] digest = {KMType.SHA1, KMType.SHA2_256}; + vals.add((short)0, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.USERID, KMInteger.uint_32(intVal, (short)0))); + vals.add((short)2, KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(val, (short)0, (short)val.length))); + vals.add((short)3, KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pubVal,(short)0))); + vals.add((short)4, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED)); + + //vals.add((short)4, KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048))); + short keyParamsPtr = KMKeyParameters.instance(arrPtr); + // Argument 2 + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + // Argument 3 + KeyPair rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + rsaKeyPair.genKeyPair(); + byte[] secret = new byte[256]; + byte[] modulus = new byte[256]; + short keyBlob = KMArray.instance((short)2); + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + short len = key.getExponent(secret, (short)0); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(secret,(short)0,len)); + len = key.getModulus(modulus, (short)0); + KMArray.cast(keyBlob).add((short)1, KMByteBlob.instance(modulus,(short)0,len)); + KMEncoder encoder = new KMEncoder(); + byte[] blob = new byte[1024]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + // Array of expected arguments + short argPtr = KMArray.instance((short) 3); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyParamsPtr); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + return argPtr; + } + + private short makeImportKeyEcCmd() { + // Argument 1 + short arrPtr = KMArray.instance((short) 4); + KMArray vals = KMArray.cast(arrPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] pubVal = {0x00, 0x01, 0x00, 0x01}; + byte[] digest = {KMType.SHA1, KMType.SHA2_256}; + vals.add((short)0, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.USERID, KMInteger.uint_32(intVal, (short)0))); + vals.add((short)2, KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(val, (short)0, (short)val.length))); + vals.add((short)3, KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pubVal,(short)0))); + //vals.add((short)4, KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048))); + short keyParamsPtr = KMKeyParameters.instance(arrPtr); + // Argument 2 + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + // Argument 3 + KeyPair ec192KeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_192); + ec192KeyPair.genKeyPair(); + byte[] secret = new byte[24]; + byte[] pubKey = new byte[52]; + short keyBlob = KMArray.instance((short)2); + ECPrivateKey key1 = (ECPrivateKey) ec192KeyPair.getPrivate(); + ECPublicKey key2 = (ECPublicKey) ec192KeyPair.getPublic(); + short len = key1.getS(secret, (short)0); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(secret,(short)0,len)); + len = key2.getW(pubKey, (short)0); + KMArray.cast(keyBlob).add((short)1, KMByteBlob.instance(pubKey,(short)0,len)); + KMEncoder encoder = new KMEncoder(); + byte[] blob = new byte[256]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + // Array of expected arguments + short argPtr = KMArray.instance((short) 3); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyParamsPtr); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + return argPtr; + } + + private short makeGetKeyCharKeyCmd() { + // Argument + short argPtr = KMArray.instance((short) 3); + KMArray vals = KMArray.cast(argPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] pubVal = {0x00, 0x01, 0x00, 0x01}; + byte[] digest = {KMType.SHA2_256}; + vals.add((short)0, keyBlob); + vals.add((short)1, KMByteBlob.instance(val, (short)0, (short)val.length)); + vals.add((short)2, KMByteBlob.instance((short)0));// No App Data + return argPtr; + } + private short makeGenerateKeyCmdHmac(byte alg, short keysize) { + // Argument + short arrPtr = KMArray.instance((short) 6); + KMArray vals = KMArray.cast(arrPtr); + byte[] val = "Test".getBytes(); + byte[] intVal = {1, 2, 3, 4}; + byte[] digest = {KMType.SHA2_256}; + vals.add((short)0, KMEnumTag.instance(KMType.ALGORITHM, alg)); + vals.add((short)1, KMIntegerTag.instance(KMType.UINT_TAG, KMType.USERID, KMInteger.uint_32(intVal, (short)0))); + vals.add((short)2, KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(val, (short)0, (short)val.length))); + vals.add((short)3, KMIntegerTag.instance(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMInteger.uint_16(keysize))); + vals.add((short)4, KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16(keysize))); + vals.add((short)5, KMEnumArrayTag.instance(KMType.DIGEST, KMByteBlob.instance(digest,(short)0, (short)digest.length))); + short keyParamsPtr = KMKeyParameters.instance(arrPtr); + // Array of expected arguments + short argPtr = KMArray.instance((short) 1); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyParamsPtr); + return argPtr; + } + + private short makeSetBootParamsCmd() { + // Argument 1 OS Version + short versionPatchPtr = KMInteger.uint_16((short)1); +// short versionTagPtr = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION,versionPatchPtr); + // Argument 2 OS Patch level +// short patchTagPtr = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, versionPatchPtr); + // Argument 3 Verified Boot Key + byte[] bootKeyHash = "00011122233344455566677788899900".getBytes(); + short bootKeyPtr = KMByteBlob.instance(bootKeyHash,(short)0, (short)bootKeyHash.length); + // Argument 4 Verified Boot Hash + short bootHashPtr = KMByteBlob.instance(bootKeyHash,(short)0, (short)bootKeyHash.length); + // Argument 5 Verified Boot State + short bootStatePtr = KMEnum.instance(KMType.VERIFIED_BOOT_STATE,KMType.VERIFIED_BOOT); + // Argument 6 Device Locked + short deviceLockedPtr = KMEnum.instance(KMType.DEVICE_LOCKED, KMType.DEVICE_LOCKED_FALSE); + // Arguments + short arrPtr = KMArray.instance((short) 6); + KMArray vals = KMArray.cast(arrPtr); + vals.add((short)0, versionPatchPtr); + vals.add((short) 1, versionPatchPtr); + vals.add((short) 2, bootKeyPtr); + vals.add((short) 3, bootHashPtr); + vals.add((short) 4, bootStatePtr); + vals.add((short) 5, deviceLockedPtr); + return arrPtr; + } + + private short makeAddRngEntropyCmd() { // Argument 1 byte[] byteBlob = new byte[32]; for (short i = 0; i < 32; i++) { byteBlob[i] = (byte) i; } - KMByteBlob keyBlob = KMByteBlob.instance(byteBlob, (short) 0, (short)byteBlob.length); + short keyBlob = KMByteBlob.instance(byteBlob, (short) 0, (short)byteBlob.length); // Array of expected arguments - return KMArray.instance((short) 1) - .add((short) 0, keyBlob); + short arrPtr = KMArray.instance((short) 1); + KMArray.cast(arrPtr).add((short) 0, keyBlob); + return arrPtr; } - } diff --git a/Applet/Applet/test/com/android/javacard/test/KMFunctionalTest.java b/Applet/Applet/test/com/android/javacard/test/KMFunctionalTest.java new file mode 100644 index 00000000..e615dd9f --- /dev/null +++ b/Applet/Applet/test/com/android/javacard/test/KMFunctionalTest.java @@ -0,0 +1,2280 @@ +/* + * 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.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.KMSEProvider; +import com.android.javacard.keymaster.KMSEProviderImpl; +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.KMError; +import com.android.javacard.keymaster.KMHardwareAuthToken; +import com.android.javacard.keymaster.KMHmacSharingParameters; +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.KMRepository; +import com.android.javacard.keymaster.KMType; +import com.android.javacard.keymaster.KMVerificationToken; +import com.licel.jcardsim.smartcardio.CardSimulator; +import com.licel.jcardsim.utils.AIDUtil; +import javacard.framework.AID; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyPair; +import javacard.security.RSAPrivateKey; +import javacard.security.RSAPublicKey; +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; +import org.junit.Assert; +import org.junit.Test; + +public class KMFunctionalTest { + private static final byte[] X509Issuer = { + 0x30, 0x76, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0C, 0x0A, 0x43, 0x61, 0x6C, 0x69, 0x66, 0x6F, + 0x72, 0x6E, 0x69, 0x61, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x0C, 0x0C, 0x47, + 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x2C, 0x20, 0x49, 0x6E, 0x63, 0x2E, 0x31, 0x10, 0x30, 0x0E, 0x06, + 0x03, 0x55, 0x04, 0x0B, 0x0C, 0x07, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x31, 0x29, 0x30, + 0x27, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0C, 0x20, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x20, + 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x4B, 0x65, 0x79 + }; + // AttestationApplicationId ::= SEQUENCE { + // * packageInfoRecords SET OF PackageInfoRecord, + // * signatureDigests SET OF OCTET_STRING, + // * } + // * + // * PackageInfoRecord ::= SEQUENCE { + // * packageName OCTET_STRING, + // * version INTEGER, + // * } + private static final byte[] attAppId = {0x30, 0x10, 0x31, 0x0B, 0x30, 0x04, 0x05, 'A', 'B', 'C', + 'D', 'E', 0x02, 0x01, 0x01, 0x31, 0x02, 0x04, 0x00}; + private static final byte[] attChallenge = {'c','h','a','l','l','e','n','g','e'}; + private static final byte[] expiryTime = {0x32,0x30,0x35,0x37,0x30,0x31,0x30,0x31,0x30,0x30,0x30,0x30,0x30,0x30,0x5A}; + private static final byte[] authKeyId = {1,2,3,4,5,6,7,8,9,1,2,3,4,5,6,7,8,9,1,2}; + + private KMSEProvider sim; + private CardSimulator simulator; + private KMEncoder encoder; + private KMDecoder decoder; + private KMSEProvider cryptoProvider; + + public KMFunctionalTest(){ + cryptoProvider = KMSEProviderImpl.instance(); + sim = KMSEProviderImpl.instance(); + simulator = new CardSimulator(); + encoder = new KMEncoder(); + decoder = new KMDecoder(); + } + + private void init(){ + // Create simulator + //KMJcardSimulator.jcardSim = true; + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMKeymasterApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + provisionCmd(simulator); + // set bootup parameters + setBootParams(simulator,(short)1,(short)1); + } + + private void setBootParams(CardSimulator simulator, short osVersion, short osPatchLevel){ + // Argument 1 OS Version + short versionPtr = KMInteger.uint_16(osVersion); +// short versionTagPtr = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION,versionPatchPtr); + // Argument 2 OS Patch level + short patchPtr = KMInteger.uint_16(osPatchLevel); + // Argument 3 Verified Boot Key + byte[] bootKeyHash = "00011122233344455566677788899900".getBytes(); + short bootKeyPtr = KMByteBlob.instance(bootKeyHash,(short)0, (short)bootKeyHash.length); + // Argument 4 Verified Boot Hash + short bootHashPtr = KMByteBlob.instance(bootKeyHash,(short)0, (short)bootKeyHash.length); + // Argument 5 Verified Boot State + short bootStatePtr = KMEnum.instance(KMType.VERIFIED_BOOT_STATE,KMType.VERIFIED_BOOT); + // Argument 6 Device Locked + short deviceLockedPtr = KMEnum.instance(KMType.DEVICE_LOCKED, KMType.DEVICE_LOCKED_FALSE); + // Arguments + short arrPtr = KMArray.instance((short) 6); + KMArray vals = KMArray.cast(arrPtr); + vals.add((short)0, versionPtr); + vals.add((short) 1, patchPtr); + vals.add((short) 2, bootKeyPtr); + vals.add((short) 3, bootHashPtr); + vals.add((short) 4, bootStatePtr); + vals.add((short) 5, deviceLockedPtr); + CommandAPDU apdu = encodeApdu((byte)0x24, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + + } + + //TODO change this + private void provisionCmd(CardSimulator simulator) { +/* // Argument 1 + short arrPtr = KMArray.instance((short) 1); + KMArray vals = KMArray.cast(arrPtr); + vals.add((short) 0, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + short keyparamsPtr = KMKeyParameters.instance(arrPtr); + // Argument 2 + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.X509); + // Argument 3 + byte[] byteBlob = new byte[48]; + for (short i = 0; i < 48; i++) { + byteBlob[i] = (byte) i; + } + short keyBlobPtr = KMByteBlob.instance(byteBlob, (short) 0, (short)byteBlob.length); + // Array of expected arguments + short argPtr = KMArray.instance((short) 3); + KMArray arg = KMArray.cast(argPtr); + arg.add((short) 0, keyparamsPtr); + arg.add((short) 1, keyFormatPtr); + arg.add((short) 2, keyBlobPtr); + CommandAPDU apdu = encodeApdu((byte)0x23, argPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); +*/ + KeyPair rsaKeyPair = cryptoProvider.createRsaKeyPair(); + byte[] pub = new byte[4]; + short len = ((RSAPublicKey)rsaKeyPair.getPublic()).getExponent(pub,(short)1); + byte[] priv = new byte[256]; + byte[] mod = new byte[256]; + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getModulus(mod,(short)0); + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getExponent(priv,(short)0); + short arrPtr = KMArray.instance((short)15); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short rsaPubExpTag = KMIntegerTag.instance(KMType.ULONG_TAG,KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pub, (short)0)); + short byteBlob1 = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob1).add((short)0, KMType.RSA_PKCS1_1_5_SIGN); + short padding = KMEnumArrayTag.instance(KMType.PADDING, byteBlob1); + short byteBlob2 = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob2).add((short)0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob2); + KMArray.cast(arrPtr).add((short)0, boolTag); + KMArray.cast(arrPtr).add((short)1, keySize); + KMArray.cast(arrPtr).add((short)2, digest); + KMArray.cast(arrPtr).add((short)3, rsaPubExpTag); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + KMArray.cast(arrPtr).add((short)5, padding); + KMArray.cast(arrPtr).add((short)6, purpose); + byte[] buf = "Attestation Id".getBytes(); + //Attestatation Ids. + KMArray.cast(arrPtr).add((short)7, KMByteTag.instance(KMType.ATTESTATION_ID_BRAND, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)8, KMByteTag.instance(KMType.ATTESTATION_ID_PRODUCT, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)9, KMByteTag.instance(KMType.ATTESTATION_ID_DEVICE, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)10, KMByteTag.instance(KMType.ATTESTATION_ID_MODEL, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)11, KMByteTag.instance(KMType.ATTESTATION_ID_IMEI, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)12, KMByteTag.instance(KMType.ATTESTATION_ID_MEID, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)13, KMByteTag.instance(KMType.ATTESTATION_ID_MANUFACTURER, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + KMArray.cast(arrPtr).add((short)14, KMByteTag.instance(KMType.ATTESTATION_ID_SERIAL, + KMByteBlob.instance(buf,(short)0, (short)buf.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW);// Note: VTS uses PKCS8 + short keyBlob = KMArray.instance((short)2); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(priv,(short)0,(short)256)); + KMArray.cast(keyBlob).add((short)1, KMByteBlob.instance(mod,(short)0,(short)256)); + byte[] blob = new byte[620]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)6); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + short byteBlob3 = KMByteBlob.instance(X509Issuer, (short)0, (short)X509Issuer.length); + arg.add((short)3, byteBlob3); + short byteBlob4 = KMByteBlob.instance(expiryTime, (short)0, (short)expiryTime.length); + arg.add((short)4, byteBlob4); + short byteBlob5 = KMByteBlob.instance(authKeyId, (short)0, (short)authKeyId.length); + arg.add((short)5, byteBlob5); + CommandAPDU apdu = encodeApdu((byte)0x23, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + } + + private void cleanUp(){ + AID appletAID1 = AIDUtil.create("A000000062"); + // Delete i.e. uninstall applet + simulator.deleteApplet(appletAID1); + } + + private CommandAPDU encodeApdu(byte ins, short cmd){ + byte[] buf = new byte[2048]; + buf[0] = (byte)0x80; + buf[1] = ins; + buf[2] = (byte)0x40; + buf[3] = (byte)0x00; + buf[4] = 0; + short len = encoder.encode(cmd, buf, (short) 7); + Util.setShort(buf, (short)5, len); + byte[] apdu = new byte[7+len]; + Util.arrayCopyNonAtomic(buf,(short)0,apdu,(short)0,(short)(7+len)); + //CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x10, 0x40, 0x00, buf, 0, actualLen); + return new CommandAPDU(apdu); + } + + @Test + public void testAesImportKeySuccess() { + init(); + byte[] aesKeySecret = new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + short arrPtr = KMArray.instance((short)5); + 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); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.AES)); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + short keyBlob = KMArray.instance((short)1); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(aesKeySecret,(short)0,(short)16)); + byte[] blob = new byte[256]; + short len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)3); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + CommandAPDU apdu = encodeApdu((byte)0x11, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + byte[] respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, hwParams); + Assert.assertEquals(KMBoolTag.cast(tag).getVal(),0x01); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 128); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.PKCS7)); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.ECB)); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.AES); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.IMPORTED); + cleanUp(); + } + + @Test + public void testHmacImportKeySuccess() { + init(); + byte[] hmacKeySecret = new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + short arrPtr = KMArray.instance((short)5); + 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.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short minMacLength = KMIntegerTag.instance(KMType.UINT_TAG,KMType.MIN_MAC_LENGTH, KMInteger.uint_16((short)256)); + KMArray.cast(arrPtr).add((short)0, boolTag); + KMArray.cast(arrPtr).add((short)1, keySize); + KMArray.cast(arrPtr).add((short)2, digest); + KMArray.cast(arrPtr).add((short)3, minMacLength); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.HMAC)); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + short keyBlob = KMArray.instance((short)1); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(hmacKeySecret,(short)0,(short)16)); + byte[] blob = new byte[256]; + short len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)3); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + CommandAPDU apdu = encodeApdu((byte)0x11, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + byte[] respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, hwParams); + Assert.assertEquals(KMBoolTag.cast(tag).getVal(),0x01); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 128); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.SHA2_256)); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 256); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.HMAC); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.IMPORTED); + cleanUp(); + } + + @Test + public void testRsaImportKeySuccess() { + init(); + KeyPair rsaKeyPair = cryptoProvider.createRsaKeyPair(); + byte[] pub = new byte[4]; + short len = ((RSAPublicKey)rsaKeyPair.getPublic()).getExponent(pub,(short)1); + byte[] priv = new byte[256]; + byte[] mod = new byte[256]; + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getModulus(mod,(short)0); + len = ((RSAPrivateKey)rsaKeyPair.getPrivate()).getExponent(priv,(short)0); + short arrPtr = KMArray.instance((short)6); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short rsaPubExpTag = KMIntegerTag.instance(KMType.ULONG_TAG,KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pub, (short)0)); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.RSA_PSS); + short padding = 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, digest); + KMArray.cast(arrPtr).add((short)3, rsaPubExpTag); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + KMArray.cast(arrPtr).add((short)5, padding); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW);// Note: VTS uses PKCS8 + short keyBlob = KMArray.instance((short)2); + KMArray.cast(keyBlob).add((short)0, KMByteBlob.instance(priv,(short)0,(short)256)); + KMArray.cast(keyBlob).add((short)1, KMByteBlob.instance(mod,(short)0,(short)256)); + byte[] blob = new byte[620]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)3); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + CommandAPDU apdu = encodeApdu((byte)0x11, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + byte[] respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, hwParams); + Assert.assertEquals(KMBoolTag.cast(tag).getVal(),0x01); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 2048); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.SHA2_256)); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.RSA_PSS)); + tag = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getSignificantShort(), 0x01); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 0x01); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.RSA); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.IMPORTED); + cleanUp(); + } + + @Test + public void testDeviceLocked(){ + init(); + byte[] hmacKey = new byte[32]; + cryptoProvider.newRandomNumber(hmacKey,(short)0,(short)32); + KMRepository.instance().initComputedHmac(hmacKey,(short)0,(short)32); + // generate aes key with unlocked_device_required + short aesKey = generateAesDesKey(KMType.AES,(short)128,null,null, true); + short keyBlobPtr = KMArray.cast(aesKey).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); + // encrypt something + short inParams = getAesDesParams(KMType.AES, KMType.ECB, KMType.PKCS7, null); + byte[] plainData= "Hello World 123!".getBytes(); + short ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.ENCRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,false, false + ); + 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); + // create verification token + short verToken = KMVerificationToken.instance(); + KMVerificationToken.cast(verToken).setTimestamp(KMInteger.uint_16((short)1)); + verToken = signVerificationToken(verToken); + // device locked request + deviceLock(verToken); + // decrypt should fail + inParams = getAesDesParams(KMType.AES, KMType.ECB, KMType.PKCS7, null); + short beginResp = begin(KMType.DECRYPT, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), KMKeyParameters.instance(inParams), (short)0); + Assert.assertEquals(beginResp,KMError.DEVICE_LOCKED); + short hwToken = KMHardwareAuthToken.instance(); + KMHardwareAuthToken.cast(hwToken).setTimestamp(KMInteger.uint_16((byte)2)); + KMHardwareAuthToken.cast(hwToken).setHwAuthenticatorType(KMEnum.instance(KMType.USER_AUTH_TYPE, (byte)KMType.PASSWORD)); + inParams = getAesDesParams(KMType.AES, KMType.ECB, KMType.PKCS7, null); + hwToken = signHwToken(hwToken); + ret = processMessage(cipherData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.DECRYPT, + KMKeyParameters.instance(inParams),hwToken,null,false, false + ); + ret = KMArray.cast(ret).get((short)0); + Assert.assertEquals(KMInteger.cast(ret).getShort(), KMError.OK); + cleanUp(); + } + + private short signHwToken(short hwToken){ + short len = 0; + byte[] scratchPad = new byte[256]; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short 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(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .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(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += 8; + // hmac the data + HMACKey key = + cryptoProvider.createHMACKey( + KMRepository.instance().getComputedHmacKey(), + (short) 0, + (short) KMRepository.instance().getComputedHmacKey().length); + byte[] mac = new byte[32]; + len = + cryptoProvider.hmacSign(key, scratchPad, (short) 0, len, + mac, + (short)0); + KMHardwareAuthToken.cast(hwToken).setMac(KMByteBlob.instance(mac,(short)0,(short)mac.length)); + return hwToken; + } + private void deviceLock(short verToken) { + short req = KMArray.instance((short)2); + KMArray.cast(req).add((short)0, KMInteger.uint_8((byte)1)); + KMArray.cast(req).add((short)1, verToken); + CommandAPDU apdu = encodeApdu((byte)0x25,req); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 1); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + byte[] respBuf = response.getBytes(); + Assert.assertEquals(respBuf[0],KMError.OK); + } + + private short signVerificationToken(short verToken) { + byte[] scratchPad = new byte[256]; + byte[] authVer = "Auth Verification".getBytes(); + //print(authVer,(short)0,(short)authVer.length); + // concatenation length will be 37 + length of verified parameters list - which is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short params = KMVerificationToken.cast(verToken).getParametersVerified(); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopy(authVer,(short)0, scratchPad, (short)0, (short)authVer.length); + short len = (short)authVer.length; + // concatenate challenge - 8 bytes + short 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(); + if (KMByteBlob.cast(ptr).length() != 0) { + len += KMByteBlob.cast(ptr).getValues(scratchPad, (short) 0); + } + // hmac the data + HMACKey key = + cryptoProvider.createHMACKey( + KMRepository.instance().getComputedHmacKey(), + (short) 0, + (short) KMRepository.instance().getComputedHmacKey().length); + ptr = KMVerificationToken.cast(verToken).getMac(); + byte[] mac = new byte[32]; + len = + cryptoProvider.hmacSign(key, scratchPad, (short) 0, len, + mac, + (short)0); + KMVerificationToken.cast(verToken).setMac(KMByteBlob.instance(mac,(short)0,(short)mac.length)); + return verToken; + } + + @Test + public void testEcImportKeySuccess() { + init(); + KeyPair ecKeyPair = cryptoProvider.createECKeyPair(); + byte[] pub = new byte[128]; + short len = ((ECPublicKey)ecKeyPair.getPublic()).getW(pub,(short)0); + short pubBlob = KMByteBlob.instance(pub,(short)0,len); + byte[] priv = new byte[32]; + len = ((ECPrivateKey)ecKeyPair.getPrivate()).getS(priv,(short)0); + short privBlob = KMByteBlob.instance(priv,(short)0,len); + short arrPtr = KMArray.instance((short)5); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)256)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short ecCurve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + KMArray.cast(arrPtr).add((short)0, boolTag); + KMArray.cast(arrPtr).add((short)1, keySize); + KMArray.cast(arrPtr).add((short)2, digest); + KMArray.cast(arrPtr).add((short)3, ecCurve); + KMArray.cast(arrPtr).add((short)4, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + short keyParams = KMKeyParameters.instance(arrPtr); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW);// Note: VTS uses PKCS8 + short keyBlob = KMArray.instance((short)2); + KMArray.cast(keyBlob).add((short)0, privBlob); + KMArray.cast(keyBlob).add((short)1, pubBlob); + byte[] blob = new byte[128]; + len = encoder.encode(keyBlob,blob,(short)0); + keyBlob = KMByteBlob.instance(blob, (short)0, len); + arrPtr = KMArray.instance((short)3); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + arg.add((short)1, keyFormatPtr); + arg.add((short)2, keyBlob); + CommandAPDU apdu = encodeApdu((byte)0x11, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + byte[] respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short blobArr = extractKeyBlobArray(KMArray.cast(ret).get((short)1)); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, hwParams); + Assert.assertEquals(KMBoolTag.cast(tag).getVal(),0x01); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 256); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.SHA2_256)); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ECCURVE, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.P_256); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.EC); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.IMPORTED); + cleanUp(); + } + + private short extractKeyBlobArray(short keyBlob) { + short ret = KMArray.instance((short) 5); + KMArray.cast(ret).add(KMKeymasterApplet.KEY_BLOB_SECRET, KMByteBlob.exp()); + KMArray.cast(ret).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, KMByteBlob.exp()); + KMArray.cast(ret).add(KMKeymasterApplet.KEY_BLOB_NONCE, KMByteBlob.exp()); + short ptr = KMKeyCharacteristics.exp(); + KMArray.cast(ret).add(KMKeymasterApplet.KEY_BLOB_KEYCHAR, ptr); + KMArray.cast(ret).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, KMByteBlob.exp()); + ret = + decoder.decodeArray( + ret, + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + short len = KMArray.cast(ret).length(); + ptr = KMArray.cast(ret).get((short)4); +// print(KMByteBlob.cast(ptr).getBuffer(),KMByteBlob.cast(ptr).getStartOff(),KMByteBlob.cast(ptr).length()); + return ret; + } + + @Test + public void testRsaGenerateKeySuccess() { + init(); + short ret = generateRsaKey(null, null); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 2048); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.DIGEST_NONE)); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.RSA_PKCS1_1_5_ENCRYPT)); + tag = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getSignificantShort(), 0x01); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 0x01); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.RSA); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.GENERATED); + cleanUp(); + } + + private short generateRsaKey(byte[] clientId, byte[] appData){ + byte[] activeAndCreationDateTime = {0,0,0x01,0x73,0x51,0x7C,(byte)0xCC,0x00}; + short tagCount = 11; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + short arrPtr = KMArray.instance(tagCount); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048)); + short byteBlob = KMByteBlob.instance((short)3); + KMByteBlob.cast(byteBlob).add((short)0, KMType.DIGEST_NONE); + KMByteBlob.cast(byteBlob).add((short)1, KMType.SHA2_256); + KMByteBlob.cast(byteBlob).add((short)2, KMType.SHA1); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + byteBlob = KMByteBlob.instance((short)5); + KMByteBlob.cast(byteBlob).add((short)0, KMType.RSA_PKCS1_1_5_ENCRYPT); + KMByteBlob.cast(byteBlob).add((short)1, KMType.RSA_PKCS1_1_5_SIGN); + KMByteBlob.cast(byteBlob).add((short)2, KMType.RSA_OAEP); + KMByteBlob.cast(byteBlob).add((short)3, KMType.RSA_PSS); + KMByteBlob.cast(byteBlob).add((short)4, KMType.PADDING_NONE); + short padding = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + byteBlob = KMByteBlob.instance((short)5); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SIGN); + KMByteBlob.cast(byteBlob).add((short)1, KMType.VERIFY); + KMByteBlob.cast(byteBlob).add((short)2, KMType.ENCRYPT); + KMByteBlob.cast(byteBlob).add((short)3, KMType.DECRYPT); + KMByteBlob.cast(byteBlob).add((short)4, KMType.WRAP_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + byte[] pub = {0,1,0,1}; + short rsaPubExpTag = KMIntegerTag.instance(KMType.ULONG_TAG,KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pub, (short)0)); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.INCLUDE_UNIQUE_ID)); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.RESET_SINCE_ID_ROTATION)); + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + KMArray.cast(arrPtr).add(tagIndex++, rsaPubExpTag); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + KMArray.cast(arrPtr).add(tagIndex++, padding); + short dateTag = KMInteger.uint_64(activeAndCreationDateTime,(short)0); + KMArray.cast(arrPtr).add(tagIndex++, KMIntegerTag.instance(KMType.DATE_TAG,KMType.ACTIVE_DATETIME,dateTag)); + KMArray.cast(arrPtr).add(tagIndex++, KMIntegerTag.instance(KMType.DATE_TAG,KMType.CREATION_DATETIME,dateTag)); + + if(clientId != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + 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); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + return ret; + } + + private short generateAttestationKey(){ + // 15th July 2020 00.00.00 + byte[] activeAndCreationDateTime = {0,0,0x01,0x73,0x51,0x7C,(byte)0xCC,0x00}; + short tagCount = 11; + short arrPtr = KMArray.instance(tagCount); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)2048)); + short byteBlob = KMByteBlob.instance((short)3); + KMByteBlob.cast(byteBlob).add((short)0, KMType.DIGEST_NONE); + KMByteBlob.cast(byteBlob).add((short)1, KMType.SHA2_256); + KMByteBlob.cast(byteBlob).add((short)2, KMType.SHA1); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.RSA_PKCS1_1_5_SIGN); + short padding = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + byte[] pub = {0,1,0,1}; + short rsaPubExpTag = KMIntegerTag.instance(KMType.ULONG_TAG,KMType.RSA_PUBLIC_EXPONENT, KMInteger.uint_32(pub, (short)0)); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.INCLUDE_UNIQUE_ID)); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.RESET_SINCE_ID_ROTATION)); + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + KMArray.cast(arrPtr).add(tagIndex++, rsaPubExpTag); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.RSA)); + KMArray.cast(arrPtr).add(tagIndex++, padding); + short dateTag = KMInteger.uint_64(activeAndCreationDateTime,(short)0); + KMArray.cast(arrPtr).add(tagIndex++, KMIntegerTag.instance(KMType.ULONG_TAG,KMType.ACTIVE_DATETIME,dateTag)); + KMArray.cast(arrPtr).add(tagIndex++, KMIntegerTag.instance(KMType.ULONG_TAG,KMType.CREATION_DATETIME,dateTag)); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + 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); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + return ret; + } + + @Test + public void testEcGenerateKeySuccess() { + init(); + short ret = generateEcKey(null, null); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 256); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.DIGEST_NONE)); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.EC); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.GENERATED); + cleanUp(); + } + public short generateEcKey(byte[] clientId, byte[] appData) { + byte[] activeAndCreationDateTime = {0,0,0x01,0x73,0x51,0x7C,(byte)0xCC,0x00}; + short tagCount = 6; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + short arrPtr = KMArray.instance(tagCount); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short)256)); + short byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.DIGEST_NONE); + KMByteBlob.cast(byteBlob).add((short)1, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SIGN); + KMByteBlob.cast(byteBlob).add((short)1, KMType.VERIFY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + short dateTag = KMInteger.uint_64(activeAndCreationDateTime,(short)0); + KMArray.cast(arrPtr).add(tagIndex++, KMIntegerTag.instance(KMType.DATE_TAG,KMType.CREATION_DATETIME,dateTag)); + if(clientId != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + 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); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + return ret; + } + + @Test + public void testHmacGenerateKeySuccess() { + init(); + short ret = generateHmacKey(null, null); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 128); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.SHA2_256)); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 160); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.HMAC); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.GENERATED); + cleanUp(); + } + public short generateHmacKey(byte[] clientId, byte[] appData){ + short tagCount = 6; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + short arrPtr = KMArray.instance(tagCount); + 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.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.SIGN); + KMByteBlob.cast(byteBlob).add((short)1, KMType.VERIFY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short minMacLen = KMIntegerTag.instance(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMInteger.uint_16((short)/*256*/160)); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, minMacLen); + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.HMAC)); + if(clientId != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + 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(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + return ret; + } + public short generateAesDesKey(byte alg, short keysize, byte[] clientId, byte[] appData, boolean unlockReqd) { + short tagCount = 7; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + if(unlockReqd)tagCount++; + short arrPtr = KMArray.instance(tagCount); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16(keysize)); + short byteBlob = KMByteBlob.instance((short)3); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ECB); + KMByteBlob.cast(byteBlob).add((short)1, KMType.CBC); + KMByteBlob.cast(byteBlob).add((short)2, KMType.CTR); + short blockModeTag = KMEnumArrayTag.instance(KMType.BLOCK_MODE, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.PKCS7); + KMByteBlob.cast(byteBlob).add((short)1, KMType.PADDING_NONE); + short paddingMode = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ENCRYPT); + KMByteBlob.cast(byteBlob).add((short)1, KMType.DECRYPT); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, blockModeTag); + KMArray.cast(arrPtr).add(tagIndex++, paddingMode); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, alg)); + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.CALLER_NONCE)); + if(unlockReqd)KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.UNLOCKED_DEVICE_REQUIRED)); + if(clientId != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + 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(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + return ret; + } + public short generateAesGcmKey(short keysize, byte[] clientId, byte[] appData) { + short tagCount = 8; + if(clientId != null) tagCount++; + if(appData != null) tagCount++; + short arrPtr = KMArray.instance(tagCount); + short boolTag = KMBoolTag.instance(KMType.NO_AUTH_REQUIRED); + short keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16(keysize)); + short macLength = KMIntegerTag.instance(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMInteger.uint_16((short)96)); + short byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.GCM); + short blockModeTag = KMEnumArrayTag.instance(KMType.BLOCK_MODE, byteBlob); + byteBlob = KMByteBlob.instance((short)1); + KMByteBlob.cast(byteBlob).add((short)0, KMType.PADDING_NONE); + short paddingMode = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ENCRYPT); + KMByteBlob.cast(byteBlob).add((short)1, KMType.DECRYPT); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, macLength); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, blockModeTag); + KMArray.cast(arrPtr).add(tagIndex++, paddingMode); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.AES)); + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.CALLER_NONCE)); + if(clientId != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_ID, KMByteBlob.instance(clientId,(short)0,(short)clientId.length))); + if(appData != null)KMArray.cast(arrPtr).add(tagIndex++, + KMByteTag.instance(KMType.APPLICATION_DATA, KMByteBlob.instance(appData,(short)0,(short)appData.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + arrPtr = KMArray.instance((short)1); + KMArray arg = KMArray.cast(arrPtr); + arg.add((short) 0, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x10, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + 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(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + return ret; + } + + @Test + public void testComputeHmacParams(){ + init(); + short params1 = KMHmacSharingParameters.instance(); + KMHmacSharingParameters.cast(params1).setSeed(KMByteBlob.instance((short)0)); + short num = KMByteBlob.instance((short)32); + cryptoProvider.newRandomNumber( + KMByteBlob.cast(num).getBuffer(), + KMByteBlob.cast(num).getStartOff(), + KMByteBlob.cast(num).length()); + KMHmacSharingParameters.cast(params1).setNonce(num); + short params2 = KMHmacSharingParameters.instance(); + KMHmacSharingParameters.cast(params2).setSeed(KMByteBlob.instance((short)0)); + num = KMByteBlob.instance((short)32); + cryptoProvider.newRandomNumber( + KMByteBlob.cast(num).getBuffer(), + KMByteBlob.cast(num).getStartOff(), + KMByteBlob.cast(num).length()); + KMHmacSharingParameters.cast(params2).setNonce(num); + short arr = KMArray.instance((short)2); + KMArray.cast(arr).add((short)0, params1); + KMArray.cast(arr).add((short)1,params2); + short arrPtr = KMArray.instance((short)1); + KMArray.cast(arrPtr).add((short)0,arr); + CommandAPDU apdu = encodeApdu((byte)0x19, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + Assert.assertEquals(0x9000, response.getSW()); + 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(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + + cleanUp(); + } + @Test + public void testGetHmacSharingParams(){ + init(); + CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x1C, 0x40, 0x00); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + KMDecoder dec = new KMDecoder(); + short ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + short inst = KMHmacSharingParameters.exp(); + KMArray.cast(ret).add((short) 1, inst); + 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(); + KMHmacSharingParameters params = KMHmacSharingParameters.cast(KMArray.cast(ret).get((short)1)); + short seed = params.getSeed(); + short nonce = params.getNonce(); + Assert.assertTrue(KMByteBlob.cast(seed).length() == 0); + Assert.assertTrue(KMByteBlob.cast(nonce).length() == 32); + //print(seed); + //print(nonce); + Assert.assertEquals(error, KMError.OK); + cleanUp(); + } + public short[] getHmacSharingParams(){ + CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x1C, 0x40, 0x00); + //print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + KMDecoder dec = new KMDecoder(); + short ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + short inst = KMHmacSharingParameters.exp(); + KMArray.cast(ret).add((short) 1, inst); + 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(); + KMHmacSharingParameters params = KMHmacSharingParameters.cast(KMArray.cast(ret).get((short)1)); + short seed = params.getSeed(); + short nonce = params.getNonce(); + return new short[]{seed, nonce}; + } + + @Test + public void testImportWrappedKey(){ + init(); + byte[] wrappedKey = new byte[16]; + cryptoProvider.newRandomNumber(wrappedKey,(short)0,(short)16); + byte[] encWrappedKey = new byte[16]; + AESKey transportKey = cryptoProvider.createAESKey((short)256); + byte[] transportKeyMaterial = new byte[32]; + cryptoProvider.newRandomNumber(transportKeyMaterial,(short)0,(short)32); + transportKey.setKey(transportKeyMaterial,(short)0); + byte[] nonce = new byte[12]; + cryptoProvider.newRandomNumber(nonce,(short)0,(short)12); + byte[] authData = "Auth Data".getBytes(); + byte[] authTag = new byte[12]; + cryptoProvider.aesGCMEncrypt(transportKey,wrappedKey,(short)0,(short)16,encWrappedKey,(short)0, + nonce,(short)0, (short)12,authData,(short)0,(short)authData.length, + authTag, (short)0, (short)12); + byte[] maskingKey = {1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0}; + byte[] maskedTransportKey = new byte[32]; + for(int i=0; i< maskingKey.length;i++){ + maskedTransportKey[i] = (byte)(transportKeyMaterial[i] ^ maskingKey[i]); + } + short rsaKeyArr = generateRsaKey(null,null); + short keyBlobPtr = KMArray.cast(rsaKeyArr).get((short)1); + byte[] wrappingKeyBlob= new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), + KMByteBlob.cast(keyBlobPtr).getStartOff(), + wrappingKeyBlob,(short)0, (short)wrappingKeyBlob.length); + short inParams = getRsaParams(KMType.SHA2_256, KMType.RSA_OAEP); + short ret = processMessage(maskedTransportKey, + KMByteBlob.instance(wrappingKeyBlob,(short)0, (short)wrappingKeyBlob.length), + KMType.ENCRYPT, + KMKeyParameters.instance(inParams), + (short)0,null,false,false + ); + keyBlobPtr = KMArray.cast(ret).get((short)2); + byte[] encTransportKey = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + Util.arrayCopyNonAtomic(KMByteBlob.cast(keyBlobPtr).getBuffer(), KMByteBlob.cast(keyBlobPtr).getStartOff(), + encTransportKey,(short)0, (short)encTransportKey.length); + short tagCount = 7; + short arrPtr = KMArray.instance(tagCount); + 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)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ECB); + KMByteBlob.cast(byteBlob).add((short)1, KMType.CBC); + short blockModeTag = KMEnumArrayTag.instance(KMType.BLOCK_MODE, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.PKCS7); + KMByteBlob.cast(byteBlob).add((short)1, KMType.PADDING_NONE); + short paddingMode = KMEnumArrayTag.instance(KMType.PADDING, byteBlob); + byteBlob = KMByteBlob.instance((short)2); + KMByteBlob.cast(byteBlob).add((short)0, KMType.ENCRYPT); + KMByteBlob.cast(byteBlob).add((short)1, KMType.DECRYPT); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + short tagIndex = 0; + KMArray.cast(arrPtr).add(tagIndex++, boolTag); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, blockModeTag); + KMArray.cast(arrPtr).add(tagIndex++, paddingMode); + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.AES)); + KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.cast(arrPtr).add(tagIndex++, KMBoolTag.instance(KMType.CALLER_NONCE)); + short keyParams = KMKeyParameters.instance(arrPtr); + short nullParams = KMArray.instance((short)0); + nullParams = KMKeyParameters.instance(nullParams); + short arr = KMArray.instance((short)12); + KMArray.cast(arr).add((short) 0, keyParams); // Key Params of wrapped key + KMArray.cast(arr).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT,KMType.RAW)); // Key Format + KMArray.cast(arr).add((short) 2, KMByteBlob.instance(encWrappedKey,(short)0,(short)encWrappedKey.length)); // Wrapped Import Key Blob + KMArray.cast(arr).add((short) 3, KMByteBlob.instance(authTag,(short)0,(short)authTag.length)); // Auth Tag + KMArray.cast(arr).add((short) 4, KMByteBlob.instance(nonce,(short)0,(short)nonce.length)); // IV - Nonce + KMArray.cast(arr).add((short) 5, KMByteBlob.instance(encTransportKey,(short)0,(short)encTransportKey.length)); // Encrypted Transport Key + KMArray.cast(arr).add((short) 6, KMByteBlob.instance(wrappingKeyBlob,(short)0, (short)wrappingKeyBlob.length)); // Wrapping Key KeyBlob + KMArray.cast(arr).add((short) 7, KMByteBlob.instance(maskingKey,(short)0,(short)maskingKey.length)); // Masking Key + KMArray.cast(arr).add((short) 8, nullParams); // Un-wrapping Params + KMArray.cast(arr).add((short) 9, KMByteBlob.instance(authData,(short)0,(short)authData.length)); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(arr).add((short) 10, KMInteger.uint_8((byte)0)); // Password Sid + KMArray.cast(arr).add((short) 11, KMInteger.uint_8((byte)0)); // Biometric Sid + CommandAPDU apdu = encodeApdu((byte)0x12, arr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + 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(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + Assert.assertEquals(0x9000, response.getSW()); + Assert.assertEquals(error, KMError.OK); + short tag = KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, hwParams); + Assert.assertEquals(KMBoolTag.cast(tag).getVal(),0x01); + tag = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, hwParams); + Assert.assertEquals(KMInteger.cast(KMIntegerTag.cast(tag).getValue()).getShort(), 128); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.PKCS7)); + tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, hwParams); + Assert.assertTrue(KMEnumArrayTag.cast(tag).contains(KMType.ECB)); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.AES); + tag = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ORIGIN, hwParams); + Assert.assertEquals(KMEnumTag.cast(tag).getValue(), KMType.SECURELY_IMPORTED); + cleanUp(); + } + + @Test + public void testGetKeyCharacteristicsWithIdDataSuccess() { + init(); + byte[] clientId = "clientId".getBytes(); + byte[] appData = "appData".getBytes(); + short ret = generateRsaKey(clientId,appData); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + short keyBlob = KMArray.cast(ret).get((short)1); + + short arrPtr = KMArray.instance((short)3); + KMArray.cast(arrPtr).add((short)0, keyBlob); + KMArray.cast(arrPtr).add((short)1, KMByteBlob.instance(clientId,(short)0, (short)clientId.length)); + KMArray.cast(arrPtr).add((short)2, KMByteBlob.instance(appData,(short)0, (short)appData.length)); + CommandAPDU apdu = encodeApdu((byte)0x1D, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + short inst = KMKeyCharacteristics.exp(); + KMArray.cast(ret).add((short) 1, inst); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + cleanUp(); + } + + @Test + public void testGetKeyCharacteristicsSuccess() { + init(); + short ret = generateRsaKey(null, null); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + short keyBlob = KMArray.cast(ret).get((short)1); + + short arrPtr = KMArray.instance((short)3); + KMArray.cast(arrPtr).add((short)0, keyBlob); + KMArray.cast(arrPtr).add((short)1, KMByteBlob.instance((short)0)); + KMArray.cast(arrPtr).add((short)2, KMByteBlob.instance((short)0)); + CommandAPDU apdu = encodeApdu((byte)0x1D, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + ret = KMArray.instance((short) 2); + KMArray.cast(ret).add((short) 0, KMInteger.exp()); + short inst = KMKeyCharacteristics.exp(); + KMArray.cast(ret).add((short) 1, inst); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + ret = decoder.decode(ret, respBuf, (short) 0, len); + error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + cleanUp(); + } + + @Test + public void testDeleteKeySuccess() { + init(); + short ret = generateRsaKey(null, null); + short keyBlobPtr = KMArray.cast(ret).get((short)1); + byte[] keyBlob = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + short len = KMByteBlob.cast(keyBlobPtr).getValues(keyBlob, (short)0); + ret = getKeyCharacteristics(keyBlobPtr); + short error = KMInteger.cast(KMArray.cast(ret).get((short)0)).getShort(); + Assert.assertEquals(error, KMError.OK); + ret = deleteKey(KMByteBlob.instance(keyBlob,(short)0,(short)keyBlob.length)); + Assert.assertEquals(ret, KMError.OK); + ret = getKeyCharacteristics(KMByteBlob.instance(keyBlob,(short)0,(short)keyBlob.length)); + short err = KMByteBlob.cast(ret).get((short)1); + Assert.assertEquals(KMError.INVALID_KEY_BLOB,err); + cleanUp(); + } + + @Test + public void testDeleteAllKeySuccess() { + init(); + short ret1 = generateRsaKey(null, null); + short keyBlobPtr = KMArray.cast(ret1).get((short)1); + byte[] keyBlob1 = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + short len = KMByteBlob.cast(keyBlobPtr).getValues(keyBlob1, (short)0); + short ret2 = generateRsaKey(null, null); + keyBlobPtr = KMArray.cast(ret2).get((short)1); + byte[] keyBlob2 = new byte[KMByteBlob.cast(keyBlobPtr).length()]; + len = KMByteBlob.cast(keyBlobPtr).getValues(keyBlob2, (short)0); + CommandAPDU apdu = new CommandAPDU(0x80, 0x17, 0x40, 0x00); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + byte[] respBuf = response.getBytes(); + Assert.assertEquals(respBuf[0], KMError.OK); + short ret = getKeyCharacteristics(KMByteBlob.instance(keyBlob1,(short)0,(short)keyBlob1.length)); + short err = KMByteBlob.cast(ret).get((short)1); + Assert.assertEquals(KMError.INVALID_KEY_BLOB,err); + ret = getKeyCharacteristics(KMByteBlob.instance(keyBlob2,(short)0,(short)keyBlob2.length)); + err = KMByteBlob.cast(ret).get((short)1); + Assert.assertEquals(KMError.INVALID_KEY_BLOB,err); + cleanUp(); + } + + private short deleteKey(short keyBlob) { + short arrPtr = KMArray.instance((short)1); + KMArray.cast(arrPtr).add((short)0, keyBlob); + CommandAPDU apdu = encodeApdu((byte)0x16, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + byte[] respBuf = response.getBytes(); + return respBuf[0]; + } + + private short abort(short opHandle) { + short arrPtr = KMArray.instance((short)1); + KMArray.cast(arrPtr).add((short)0, opHandle); + CommandAPDU apdu = encodeApdu((byte)0x22, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + byte[] respBuf = response.getBytes(); + return respBuf[0]; + } + + public short getKeyCharacteristics(short keyBlob){ + short arrPtr = KMArray.instance((short)3); + KMArray.cast(arrPtr).add((short)0, keyBlob); + KMArray.cast(arrPtr).add((short)1, KMByteBlob.instance((short)0)); + KMArray.cast(arrPtr).add((short)2, KMByteBlob.instance((short)0)); + CommandAPDU apdu = encodeApdu((byte)0x1D, arrPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + 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); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + if( len > 5) + ret = decoder.decode(ret, respBuf, (short) 0, len); + else + ret = KMByteBlob.instance(respBuf, (short)0, len); + return ret; + } + + @Test + public void testWithAesGcmWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.GCM, KMType.PADDING_NONE,true); + cleanUp(); + } + @Test + public void testWithAesEcbPkcs7WithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.ECB, KMType.PKCS7,true); + cleanUp(); + } + + @Test + public void testWithAesCtrNoPadWithUpdate(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CTR, KMType.PADDING_NONE,true); + cleanUp(); + } + + @Test + public void testWithAesCtrNoPad(){ + init(); + testEncryptDecryptWithAesDes(KMType.AES, KMType.CTR, KMType.PADDING_NONE,false); + 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(); + } + + @Test + public void testWithRsa256Oaep(){ + init(); + testEncryptDecryptWithRsa(KMType.SHA2_256, KMType.RSA_OAEP); + cleanUp(); + } + @Test + public void testWithRsaSha1Oaep(){ + init(); + testEncryptDecryptWithRsa(KMType.SHA1, 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 testSignWithRsaNoneNoPad(){ + init(); + testSignVerifyWithRsa(KMType.DIGEST_NONE, KMType.PADDING_NONE,false, false); + cleanUp(); + } + @Test + public void testSignWithRsaNonePkcs1(){ + init(); + testSignVerifyWithRsa(KMType.DIGEST_NONE, KMType.RSA_PKCS1_1_5_SIGN,false, false); + 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, true); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaSHA256Pss(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PSS,false, true); + cleanUp(); + } + @Test + public void testSignVerifyWithRsaSHA256Pkcs1WithUpdate(){ + init(); + testSignVerifyWithRsa(KMType.SHA2_256, KMType.RSA_PKCS1_1_5_SIGN,true, true); + cleanUp(); + } + + @Test + public void testProvisionSuccess(){ + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMKeymasterApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + provisionCmd(simulator); + cleanUp(); + } + @Test + public void testAttestRsaKey(){ + init(); + short key = generateRsaKey(null,null); + short keyBlobPtr = KMArray.cast(key).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); + testAttestKey(keyBlob); + cleanUp(); + } + + @Test + public void testAttestEcKey(){ + init(); + short key = generateEcKey(null,null); + short keyBlobPtr = KMArray.cast(key).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); + testAttestKey(keyBlob); + cleanUp(); + } + + public void testAttestKey(byte[] keyBlob){ + /* + short key = generateRsaKey(null,null); + short keyBlobPtr = KMArray.cast(key).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 arrPtr = KMArray.instance((short)2); + KMArray.cast(arrPtr).add((short)0, KMByteTag.instance(KMType.ATTESTATION_APPLICATION_ID, + KMByteBlob.instance(attAppId,(short)0,(short)attAppId.length))); + KMArray.cast(arrPtr).add((short)1, KMByteTag.instance(KMType.ATTESTATION_CHALLENGE, + KMByteBlob.instance(attChallenge,(short)0,(short)attChallenge.length))); + short keyParams = KMKeyParameters.instance(arrPtr); + short args = KMArray.instance((short)2); + KMArray.cast(args).add((short)0, KMByteBlob.instance(keyBlob,(short)0,(short)keyBlob.length)); + KMArray.cast(args).add((short)1, keyParams); + CommandAPDU apdu = encodeApdu((byte)0x14, args); + //print(apdu.getBytes(),(short)0,(short)apdu.getBytes().length); + ResponseAPDU response = simulator.transmitCommand(apdu); + short ret = KMArray.instance((short) 2); + short arrBlobs = KMArray.instance((short)1); + KMArray.cast(arrBlobs).add((short)0, KMByteBlob.exp()); + KMArray.cast(ret).add((short)0, KMInteger.exp()); + KMArray.cast(ret).add((short)1, arrBlobs); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + //(respBuf,(short)0,(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); + arrBlobs = KMArray.cast(ret).get((short)1); + short cert = KMArray.cast(arrBlobs).get((short)0); + //printCert(KMByteBlob.cast(cert).getBuffer(),KMByteBlob.cast(cert).getStartOff(),KMByteBlob.cast(cert).length()); + } + + @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(); + } + + @Test + public void testDestroyAttIds(){ + init(); + CommandAPDU commandAPDU = new CommandAPDU(0x80, 0x1A, 0x40, 0x00); + ResponseAPDU response = simulator.transmitCommand(commandAPDU); + byte[] respBuf = response.getBytes(); + Assert.assertEquals(respBuf[0], 0); + 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, true); + cleanUp(); + } + @Test + public void testAbortOperation(){ + init(); + short aesDesKeyArr = generateAesDesKey(KMType.AES, (short)128,null, null, false);; + 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 = 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; + boolean aesGcmFlag = false; + if(alg == KMType.AES){ + if(blockMode == KMType.GCM){ + aesDesKeyArr = generateAesGcmKey((short)128,null,null); + aesGcmFlag = true; + } else { + aesDesKeyArr = generateAesDesKey(alg, (short) 128, null, null, false); + } + } else{ + aesDesKeyArr = generateAesDesKey(alg, (short)168,null, null, false); + } + 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, aesGcmFlag + ); + inParams = getAesDesParams(alg,blockMode, padding, nonce); + keyBlobPtr = KMArray.cast(ret).get((short)2); + //print(keyBlobPtr); + 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, aesGcmFlag + ); + keyBlobPtr = KMArray.cast(ret).get((short)2); + //print(plainData,(short)0,(short)plainData.length); + //print(keyBlobPtr); + 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, 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,false + ); + keyBlobPtr = KMArray.cast(ret).get((short)2); + short len = KMByteBlob.cast(keyBlobPtr).length(); + short start = KMByteBlob.cast(keyBlobPtr).getStartOff(); + short equal = Util.arrayCompare(plainData,(short)0,KMByteBlob.cast(keyBlobPtr).getBuffer(), + (short)(start+len-plainData.length),(short)plainData.length); + Assert.assertTrue(equal == 0); + } + + public void testSignVerifyWithRsa(byte digest, byte padding, boolean update, boolean verifyFlag){ + 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,false + ); + 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); + if(verifyFlag == false) { + Assert.assertEquals(signatureData.length,256); + return; + } + ret = processMessage(plainData, + KMByteBlob.instance(keyBlob,(short)0, (short)keyBlob.length), + KMType.VERIFY, + KMKeyParameters.instance(inParams), + (short)0,signatureData,update,false + ); + 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,false + ); + 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,false + ); + 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,true); + 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,false + ); + inParams = getHmacParams(digest,false); + 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,false + ); + 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.GCM){ + inParams = KMArray.instance((short)5); + 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 = 12; + byteBlob = KMByteBlob.instance(nonce,(short)0, nonceLen); + KMArray.cast(inParams).add((short)2, KMByteTag.instance(KMType.NONCE, byteBlob)); + short macLen = KMInteger.uint_16((short)128); + macLen = KMIntegerTag.instance(KMType.UINT_TAG,KMType.MAC_LENGTH,macLen); + KMArray.cast(inParams).add((short)3, macLen); + byte[] authData = "AuthData".getBytes(); + short associatedData = KMByteBlob.instance(authData,(short)0,(short)authData.length); + associatedData = KMByteTag.instance(KMType.ASSOCIATED_DATA,associatedData); + KMArray.cast(inParams).add((short)4, associatedData); + }else 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, boolean sign) { + short paramsize = (short) (sign ? 2 : 1); + short inParams = KMArray.instance((short)paramsize); + 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*/160)); + if(sign) + 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, + boolean aesGcmFlag) { + 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; + inParams = 0; + //Test + short firstDataLen =16; + if (keyPurpose == KMType.DECRYPT) { + firstDataLen = 32; + } + + //Test + + if (updateFlag) { + dataPtr = KMByteBlob.instance(data, (short) 0, (short) /*16*/firstDataLen); + if(aesGcmFlag){ + byte[] authData = "AuthData".getBytes(); + short associatedData = KMByteBlob.instance(authData,(short)0,(short)authData.length); + associatedData = KMByteTag.instance(KMType.ASSOCIATED_DATA,associatedData); + inParams = KMArray.instance((short)1); + KMArray.cast(inParams).add((short)0, associatedData); + inParams = KMKeyParameters.instance(inParams); + } + ret = update(KMInteger.uint_16(opHandle), dataPtr, inParams, (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*/firstDataLen, (short) (data.length - /*16*/firstDataLen)); + } + } + + 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(apdu.getBytes(),(short)0,(short)apdu.getBytes().length); + 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; + 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); + return ret;}else{ + if(len == 3) return respBuf[0]; + if(len == 4) return respBuf[1]; + return Util.getShort(respBuf,(short)0); + } + } + + 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; + } + + private void print(short blob){ + print(KMByteBlob.cast(blob).getBuffer(),KMByteBlob.cast(blob).getStartOff(),KMByteBlob.cast(blob).length()); + } + private void print(byte[] buf, short start, short length){ + StringBuilder sb = new StringBuilder(); + for(int i = start; i < (start+length); i++){ + sb.append(String.format(" 0x%02X", buf[i])) ; + } + System.out.println(sb.toString()); + } + private void printCert(byte[] buf, short start, short length){ + StringBuilder sb = new StringBuilder(); + for(int i = start; i < (start+length); i++){ + sb.append(String.format("%02X", buf[i])) ; + } + System.out.println(sb.toString()); + } + + +/* + @Test + public void testApdu(){ + init(); + byte[] cmd = {(byte)0x80,0x11,0x40,0x00,0x00,0x00,0x4C,(byte)0x83,(byte)0xA5,0x1A,0x70,0x00,0x01,(byte)0xF7,0x01,0x1A,0x10, + 0x00,0x00,0x02,0x03,0x1A,0x30,0x00,0x00,0x03,0x19,0x01,0x00,0x1A,0x20,0x00,0x00,0x01,0x42,0x02, + 0x03,0x1A,0x20,0x00,0x00,0x05,0x41,0x04,0x03,0x58,0x24,(byte)0x82,0x58,0x20,0x73,0x7C,0x2E,(byte)0xCD, + 0x7B,(byte)0x8D,0x19,0x40,(byte)0xBF,0x29,0x30,(byte)0xAA,(byte)0x9B,0x4E, + (byte)0xD3,(byte)0xFF,(byte)0x94,0x1E,(byte)0xED,0x09,0x36,0x6B, + (byte)0xC0,0x32,(byte)0x99,(byte)0x98,0x64,(byte)0x81,(byte)0xF3,(byte)0xA4,(byte)0xD8,0x59,0x40}; + CommandAPDU cmdApdu = new CommandAPDU(cmd); + ResponseAPDU resp = simulator.transmitCommand(cmdApdu); + 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); + byte[] respBuf = resp.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(); + short keyBlobLength = KMByteBlob.cast(KMArray.cast(ret).get((short)1)).length(); + short blobArr = extractKeyBlobArray(KMArray.cast(ret).get((short)1)); + short keyCharacteristics = KMArray.cast(ret).get((short)2); + short hwParams = KMKeyCharacteristics.cast(keyCharacteristics).getHardwareEnforced(); + short swParams = KMKeyCharacteristics.cast(keyCharacteristics).getSoftwareEnforced(); + cleanUp(); + } + */ +} 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..6e4f103f 100644 --- a/Applet/default.output +++ b/Applet/default.output @@ -4,6 +4,17 @@ OUTPUT OFF; 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: 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: 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: 69, SW2: 85 +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: 00, SW1: 69, SW2: 86 +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: 69, SW2: 86 +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: 00, SW1: 69, SW2: 86 +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: 00, SW1: 69, SW2: 86 +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: 00, SW1: 69, SW2: 86 +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: 00, SW1: 69, SW2: 86 +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: 00, SW1: 69, SW2: 85 +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: 00, SW1: 69, SW2: 85 +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: 00, SW1: 69, SW2: 85 +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: 00, SW1: 69, SW2: 85 +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: 00, SW1: 69, SW2: 85 +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: 00, SW1: 69, SW2: 85 diff --git a/README.md b/README.md index 338c11d6..dd532000 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # JavaCardKeymaster JavaCard implementation of the [Android Keymaster 4.1 HAL](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.1/IKeymasterDevice.hal) (most of the specification is in the [Android Keymaster 4.0 HAL](https://android.googlesource.com/platform/hardware/interfaces/+/master/keymaster/4.0/IKeymasterDevice.hal)), intended for creation of StrongBox Keymaster instances to support the [Android Hardware-backed Keystore](https://source.android.com/security/keystore). -[Design doc](https://docs.google.com/document/d/1mIv895E3imKfzxS9weWQxP3jZIxS-7OPmh4fxdjOPm0/edit#heading=h.xgjl2srtytjt) (much of the content will move here when it stablizes, for now this is a limited-access link). +Here is the [JavaCard Applet design doc](https://docs.google.com/document/d/1mIv895E3imKfzxS9weWQxP3jZIxS-7OPmh4fxdjOPm0/edit#heading=h.xgjl2srtytjt) and the [HAL design doc](https://docs.google.com/document/d/1ExaoEOU3mkjhoMIcAhD_Z6kPp5yyfjpnfBpUQHiSomw/edit#heading=h.gjdgxs) (the content will move here when it stablizes, for now these are a limited-access links).