/*
 * 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.seprovider;

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.KeyAgreement;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.MessageDigest;
import javacard.security.RSAPrivateKey;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.AEADCipher;
import javacardx.crypto.Cipher;
import org.globalplatform.upgrade.Element;
import org.globalplatform.upgrade.UpgradeManager;

/**
 * This class implements KMSEProvider and provides all the necessary crypto operations required to
 * support the KeyMint specification. This class supports AES, 3DES, HMAC, RSA, ECDSA, ECDH
 * algorithms additionally it also supports ECDSA_NO_DIGEST, RSA_NO_DIGEST and RSA_OAEP_MGF1_SHA1
 * and RSA_OAEP_MGF1_SHA256 algorithms. This class follows the pattern of Init-Update-Final for the
 * crypto operations.
 */
public class KMAndroidSEProvider implements KMSEProvider {

  // The tag length for AES GCM algorithm.
  public static final byte AES_GCM_TAG_LENGTH = 16;
  // The nonce length for AES GCM algorithm.
  public static final byte AES_GCM_NONCE_LENGTH = 12;
  // AES keysize offsets in aesKeys[] for 128 and 256 sizes respectively.
  public static final byte KEYSIZE_128_OFFSET = 0x00;
  public static final byte KEYSIZE_256_OFFSET = 0x01;
  // The size of the temporary buffer.
  public static final short TMP_ARRAY_SIZE = 300;
  // The length of the rsa key in bytes.
  private static final short RSA_KEY_SIZE = 256;
  // Below are the flag to denote device reset events
  public static final byte POWER_RESET_FALSE = (byte) 0xAA;
  public static final byte POWER_RESET_TRUE = (byte) 0x00;
  // The computed HMAC key size.
  private static final byte COMPUTED_HMAC_KEY_SIZE = 32;
  // The constant 'L' as defiend in
  // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf, page 12.
  private static byte[] CMAC_KDF_CONSTANT_L;
  // Constant to represent 0.
  private static byte[] CMAC_KDF_CONSTANT_ZERO;
  // KeyAgreement instance.
  private static KeyAgreement keyAgreement;

  // AESKey
  private AESKey aesKeys[];
  // DES3Key
  private DESKey triDesKey;
  // HMACKey
  private HMACKey hmacKey;
  // RSA Key Pair
  private KeyPair rsaKeyPair;
  // EC Key Pair.
  private KeyPair ecKeyPair;
  // Temporary array.
  public byte[] tmpArray;
  // This is used for internal encryption/decryption operations.
  private static AEADCipher aesGcmCipher;
  // Instance of Signature algorithm used in KDF.
  private Signature kdf;
  // Flag used to denote the power reset event.
  public static byte[] resetFlag;
  // Instance of HMAC Signature algorithm.
  private Signature hmacSignature;
  // For ImportwrappedKey operations.
  private KMRsaOAEPEncoding rsaOaepDecipher;
  // Instance of pool manager.
  private KMPoolManager poolMgr;
  // Instance of KMOperationImpl used only to encrypt/decrypt the KeyBlobs.
  private KMOperationImpl globalOperation;
  // Entropy
  private RandomData rng;
  // Singleton instance.
  private static KMAndroidSEProvider androidSEProvider = null;

  public static KMAndroidSEProvider getInstance() {
    return androidSEProvider;
  }

  public KMAndroidSEProvider() {
    initStatics();
    // Re-usable AES,DES and HMAC keys in persisted memory.
    aesKeys = new AESKey[2];
    aesKeys[KEYSIZE_128_OFFSET] =
        (AESKey)
            KeyBuilder.buildKey(
                KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false);
    aesKeys[KEYSIZE_256_OFFSET] =
        (AESKey)
            KeyBuilder.buildKey(
                KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false);
    triDesKey =
        (DESKey)
            KeyBuilder.buildKey(
                KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false);
    hmacKey =
        (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false);
    rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048);
    ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256);
    keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false);
    poolMgr = KMPoolManager.getInstance();
    poolMgr.initECKey(ecKeyPair);
    // RsaOAEP Decipher
    rsaOaepDecipher = new KMRsaOAEPEncoding(KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1);

    kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false);
    hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false);

    globalOperation = new KMOperationImpl();

    // Temporary transient array created to use locally inside functions.
    tmpArray = JCSystem.makeTransientByteArray(TMP_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT);
    Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0);
    // Random number generator initialisation.
    rng = RandomData.getInstance(RandomData.ALG_KEYGENERATION);
    androidSEProvider = this;
    resetFlag = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET);
    resetFlag[0] = (byte) POWER_RESET_FALSE;
  }

  void initStatics() {
    CMAC_KDF_CONSTANT_L = new byte[] {0x00, 0x00, 0x01, 0x00};
    CMAC_KDF_CONSTANT_ZERO = new byte[] {0x00};
  }

  public void clean() {
    Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0);
  }

  public AESKey createAESKey(short keysize) {
    try {
      if (keysize > TMP_ARRAY_SIZE) {
        KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
      }
      newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8));
      return createAESKey(tmpArray, (short) 0, (short) (keysize / 8));
    } finally {
      clean();
    }
  }

  public AESKey createAESKey(byte[] buf, short startOff, short length) {
    AESKey key = null;
    short keysize = (short) (length * 8);
    if (keysize == 128) {
      key = (AESKey) aesKeys[KEYSIZE_128_OFFSET];
      key.setKey(buf, (short) startOff);
    } else if (keysize == 256) {
      key = (AESKey) aesKeys[KEYSIZE_256_OFFSET];
      key.setKey(buf, (short) startOff);
    } else {
      KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
    }
    return key;
  }

  public DESKey createTDESKey() {
    try {
      newRandomNumber(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8));
      return createTDESKey(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8));
    } finally {
      clean();
    }
  }

  public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) {
    triDesKey.setKey(secretBuffer, secretOff);
    return triDesKey;
  }

  public HMACKey createHMACKey(short keysize) {
    // As per the KeyMint2.0 specification
    // The minimum supported HMAC key size is 64 bits
    // The maximum supported HMAC key size is 512 bits
    // The keysize should be a multiple of 8.
    if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    try {
      newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8));
      return createHMACKey(tmpArray, (short) 0, (short) (keysize / 8));
    } finally {
      clean();
    }
  }

  public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) {
    hmacKey.setKey(secretBuffer, secretOff, secretLength);
    return hmacKey;
  }

  public KeyPair createRsaKeyPair() {
    rsaKeyPair.genKeyPair();
    return rsaKeyPair;
  }

  public RSAPrivateKey createRsaKey(
      byte[] modBuffer,
      short modOff,
      short modLength,
      byte[] privBuffer,
      short privOff,
      short privLength) {
    RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate();
    privKey.setExponent(privBuffer, privOff, privLength);
    privKey.setModulus(modBuffer, modOff, modLength);
    return privKey;
  }

  public KeyPair createECKeyPair() {
    ecKeyPair.genKeyPair();
    return ecKeyPair;
  }

  public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) {
    ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate();
    privKey.setS(privBuffer, privOff, privLength);
    return privKey;
  }

  @Override
  public short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) {
    switch (alg) {
      case KMType.AES:
        AESKey aesKey = createAESKey(keysize);
        return aesKey.getKey(buf, startOff);
      case KMType.DES:
        DESKey desKey = createTDESKey();
        return desKey.getKey(buf, startOff);
      case KMType.HMAC:
        HMACKey hmacKey = createHMACKey(keysize);
        return hmacKey.getKey(buf, startOff);
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
    return 0;
  }

  @Override
  public void createAsymmetricKey(
      byte alg,
      byte[] privKeyBuf,
      short privKeyStart,
      short privKeyLength,
      byte[] pubModBuf,
      short pubModStart,
      short pubModLength,
      short[] lengths) {
    switch (alg) {
      case KMType.RSA:
        if (RSA_KEY_SIZE != privKeyLength || RSA_KEY_SIZE != pubModLength) {
          CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
        }
        KeyPair rsaKey = createRsaKeyPair();
        RSAPrivateKey privKey = (RSAPrivateKey) rsaKey.getPrivate();
        // Copy exponent.
        Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0);
        lengths[0] = privKey.getExponent(tmpArray, (short) 0);
        if (lengths[0] > privKeyLength) {
          CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
        }
        Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0);
        Util.arrayCopyNonAtomic(
            tmpArray,
            (short) 0,
            privKeyBuf,
            (short) (privKeyStart + privKeyLength - lengths[0]),
            lengths[0]);
        // Copy modulus
        Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0);
        lengths[1] = privKey.getModulus(tmpArray, (short) 0);
        if (lengths[1] > pubModLength) {
          CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
        }
        Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0);
        Util.arrayCopyNonAtomic(
            tmpArray,
            (short) 0,
            pubModBuf,
            (short) (pubModStart + pubModLength - lengths[1]),
            lengths[1]);
        break;
      case KMType.EC:
        KeyPair ecKey = createECKeyPair();
        ECPublicKey ecPubKey = (ECPublicKey) ecKey.getPublic();
        ECPrivateKey ecPrivKey = (ECPrivateKey) ecKey.getPrivate();
        lengths[0] = ecPrivKey.getS(privKeyBuf, privKeyStart);
        lengths[1] = ecPubKey.getW(pubModBuf, pubModStart);
        if (lengths[0] > privKeyLength || lengths[1] > pubModLength) {
          CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
        }
        break;
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
  }

  @Override
  public boolean importSymmetricKey(
      byte alg, short keysize, byte[] buf, short startOff, short length) {
    switch (alg) {
      case KMType.AES:
        createAESKey(buf, startOff, length);
        break;
      case KMType.DES:
        createTDESKey(buf, startOff, length);
        break;
      case KMType.HMAC:
        createHMACKey(buf, startOff, length);
        break;
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
    return true;
  }

  @Override
  public boolean importAsymmetricKey(
      byte alg,
      byte[] privKeyBuf,
      short privKeyStart,
      short privKeyLength,
      byte[] pubModBuf,
      short pubModStart,
      short pubModLength) {
    switch (alg) {
      case KMType.RSA:
        createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, privKeyStart, privKeyLength);
        break;
      case KMType.EC:
        createEcKey(privKeyBuf, privKeyStart, privKeyLength);
        break;
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
    return true;
  }

  @Override
  public void getTrueRandomNumber(byte[] buf, short start, short length) {
    newRandomNumber(buf, start, length);
  }

  @Override
  public void newRandomNumber(byte[] num, short startOff, short length) {
    rng.nextBytes(num, startOff, length);
  }

  @Override
  public void addRngEntropy(byte[] num, short offset, short length) {
    rng.setSeed(num, offset, length);
  }

  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 (authTagLen != AES_GCM_TAG_LENGTH) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    if (nonceLen != AES_GCM_NONCE_LENGTH) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    if (aesGcmCipher == null) {
      aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false);
    }
    aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen);
    if (authDataLen != 0) {
      aesGcmCipher.updateAAD(authData, authDataStart, authDataLen);
    }
    short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart);
    aesGcmCipher.retrieveTag(authTag, authTagStart, authTagLen);
    return ciphLen;
  }

  @Override
  public short aesGCMEncrypt(
      byte[] aesKey,
      short aesKeyStart,
      short aesKeyLen,
      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) {

    AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen);
    return aesGCMEncrypt(
        key,
        secret,
        secretStart,
        secretLen,
        encSecret,
        encSecretStart,
        nonce,
        nonceStart,
        nonceLen,
        authData,
        authDataStart,
        authDataLen,
        authTag,
        authTagStart,
        authTagLen);
  }

  @Override
  public boolean aesGCMDecrypt(
      byte[] aesKey,
      short aesKeyStart,
      short aesKeyLen,
      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 (aesGcmCipher == null) {
      aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false);
    }
    boolean verification = false;
    AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen);
    aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen);
    if (authDataLen != 0) {
      aesGcmCipher.updateAAD(authData, authDataStart, authDataLen);
    }
    // encrypt the secret
    aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart);
    verification =
        aesGcmCipher.verifyTag(
            authTag, authTagStart, (short) authTagLen, (short) AES_GCM_TAG_LENGTH);
    return verification;
  }

  public HMACKey cmacKdf(
      KMKey preSharedKey,
      byte[] label,
      short labelStart,
      short labelLen,
      byte[] context,
      short contextStart,
      short contextLength) {
    // Note: the variables i and L correspond to i and L in the standard.  See page 12 of
    // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf.
    try {
      // This is hardcoded to requirement - 32 byte output with two concatenated
      // 16 bytes K1 and K2.
      final byte n = 2; // hardcoded

      // [i] counter - 32 bits
      short iBufLen = 4;
      short keyOutLen = n * 16;
      // Convert Hmackey to AES Key as the algorithm is ALG_AES_CMAC_128.
      KMHmacKey hmacKey = ((KMHmacKey) preSharedKey);
      hmacKey.hmacKey.getKey(tmpArray, (short) 0);
      aesKeys[KEYSIZE_256_OFFSET].setKey(tmpArray, (short) 0);
      // Initialize the key derivation function.
      kdf.init(aesKeys[KEYSIZE_256_OFFSET], Signature.MODE_SIGN);
      // Clear the tmpArray buffer.
      Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0);

      Util.arrayFillNonAtomic(tmpArray, (short) 0, iBufLen, (byte) 0);
      Util.arrayFillNonAtomic(tmpArray, (short) iBufLen, keyOutLen, (byte) 0);

      byte i = 1;
      short pos = 0;
      while (i <= n) {
        tmpArray[3] = i;
        // 4 bytes of iBuf with counter in it
        kdf.update(tmpArray, (short) 0, (short) iBufLen);
        kdf.update(label, labelStart, (short) labelLen); // label
        kdf.update(
            CMAC_KDF_CONSTANT_ZERO,
            (short) 0,
            (short) CMAC_KDF_CONSTANT_ZERO.length); // 1 byte of 0x00
        kdf.update(context, contextStart, contextLength); // context
        // 4 bytes of L - signature of 16 bytes
        pos =
            kdf.sign(
                CMAC_KDF_CONSTANT_L,
                (short) 0,
                (short) CMAC_KDF_CONSTANT_L.length,
                tmpArray,
                (short) (iBufLen + pos));
        i++;
      }
      return createHMACKey(tmpArray, (short) iBufLen, (short) keyOutLen);
    } finally {
      clean();
    }
  }

  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 short hmacSign(
      byte[] keyBuf,
      short keyStart,
      short keyLength,
      byte[] data,
      short dataStart,
      short dataLength,
      byte[] mac,
      short macStart) {
    HMACKey key = createHMACKey(keyBuf, keyStart, keyLength);
    return hmacSign(key, data, dataStart, dataLength, mac, macStart);
  }

  @Override
  public short hmacSign(
      Object key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) {
    if (!(key instanceof KMHmacKey)) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }
    KMHmacKey hmacKey = (KMHmacKey) key;
    return hmacSign(hmacKey.hmacKey, data, dataStart, dataLength, mac, macStart);
  }

  @Override
  public short hmacKDF(
      KMKey masterkey,
      byte[] data,
      short dataStart,
      short dataLength,
      byte[] signature,
      short signatureStart) {
    try {
      KMAESKey aesKey = (KMAESKey) masterkey;
      short keyLen = (short) (aesKey.aesKey.getSize() / 8);
      aesKey.aesKey.getKey(tmpArray, (short) 0);
      return hmacSign(
          tmpArray, (short) 0, keyLen, data, dataStart, dataLength, signature, signatureStart);
    } finally {
      clean();
    }
  }

  @Override
  public boolean hmacVerify(
      KMKey key,
      byte[] data,
      short dataStart,
      short dataLength,
      byte[] mac,
      short macStart,
      short macLength) {
    KMHmacKey hmacKey = (KMHmacKey) key;
    hmacSignature.init(hmacKey.hmacKey, Signature.MODE_VERIFY);
    return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength);
  }

  @Override
  public short rsaDecipherOAEP256(
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] modBuffer,
      short modOff,
      short modLength,
      byte[] inputDataBuf,
      short inputDataStart,
      short inputDataLength,
      byte[] outputDataBuf,
      short outputDataStart) {
    RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate();
    key.setExponent(secret, (short) secretStart, (short) secretLength);
    key.setModulus(modBuffer, (short) modOff, (short) modLength);
    rsaOaepDecipher.init(key, Cipher.MODE_DECRYPT);
    return rsaOaepDecipher.doFinal(
        inputDataBuf,
        (short) inputDataStart,
        (short) inputDataLength,
        outputDataBuf,
        (short) outputDataStart);
  }

  private byte mapSignature256Alg(byte alg, byte padding, byte digest) {
    switch (alg) {
      case KMType.RSA:
        switch (padding) {
          case KMType.RSA_PKCS1_1_5_SIGN:
            {
              if (digest == KMType.DIGEST_NONE) {
                return KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST;
              } else {
                return Signature.ALG_RSA_SHA_256_PKCS1;
              }
            }
          case KMType.RSA_PSS:
            return Signature.ALG_RSA_SHA_256_PKCS1_PSS;
          case KMType.PADDING_NONE:
            return KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD;
        }
        break;
      case KMType.EC:
        if (digest == KMType.DIGEST_NONE) {
          return KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST;
        } else {
          return Signature.ALG_ECDSA_SHA_256;
        }
      case KMType.HMAC:
        return Signature.ALG_HMAC_SHA_256;
    }
    return -1;
  }

  private byte mapCipherAlg(byte alg, byte padding, byte blockmode, byte digest) {
    switch (alg) {
      case KMType.AES:
        switch (blockmode) {
          case KMType.ECB:
            return Cipher.ALG_AES_BLOCK_128_ECB_NOPAD;
          case KMType.CBC:
            return Cipher.ALG_AES_BLOCK_128_CBC_NOPAD;
          case KMType.CTR:
            return Cipher.ALG_AES_CTR;
          case KMType.GCM:
            return AEADCipher.ALG_AES_GCM;
        }
        break;
      case KMType.DES:
        switch (blockmode) {
          case KMType.ECB:
            return Cipher.ALG_DES_ECB_NOPAD;
          case KMType.CBC:
            return Cipher.ALG_DES_CBC_NOPAD;
        }
        break;
      case KMType.RSA:
        switch (padding) {
          case KMType.PADDING_NONE:
            return Cipher.ALG_RSA_NOPAD;
          case KMType.RSA_PKCS1_1_5_ENCRYPT:
            return Cipher.ALG_RSA_PKCS1;
          case KMType.RSA_OAEP:
            {
              if (digest == KMType.SHA1) {
                /* MGF Digest is SHA1 */
                return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1;
              } else if (digest == KMType.SHA2_256) {
                /* MGF Digest is SHA256 */
                return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256;
              } else {
                KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
              }
            }
        }
        break;
    }
    return -1;
  }

  public KMOperation createSymmetricCipher(
      short alg,
      short purpose,
      short macLength,
      short blockMode,
      short padding,
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] ivBuffer,
      short ivStart,
      short ivLength,
      boolean isRkp) {

    short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode, (byte) 0);
    KMOperation operation = null;
    if (isRkp) {
      operation = poolMgr.getRKpOperation(purpose, cipherAlg, alg, padding, blockMode, macLength);
    } else {
      operation =
          poolMgr.getOperationImpl(
              purpose, cipherAlg, alg, padding, blockMode, macLength, secretLength, false);
    }
    // Get the KeyObject from the operation and update the key with the secret key material.
    KMKeyObject keyObj = operation.getKeyObject();
    Key key = (Key) keyObj.keyObjectInst;
    switch (secretLength) {
      case 32:
      case 16:
        ((AESKey) key).setKey(secret, secretStart);
        break;
      case 24:
        ((DESKey) key).setKey(secret, secretStart);
        break;
      default:
        CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
        break;
    }
    ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, ivBuffer, ivStart, ivLength);
    return operation;
  }

  public KMOperation createHmacSignerVerifier(
      short purpose,
      short digest,
      byte[] secret,
      short secretStart,
      short secretLength,
      boolean isRkp) {
    KMOperation operation = null;
    if (digest != KMType.SHA2_256) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    if (isRkp) {
      operation =
          poolMgr.getRKpOperation(
              purpose,
              Signature.ALG_HMAC_SHA_256,
              KMType.HMAC,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE);
    } else {
      operation =
          poolMgr.getOperationImpl(
              purpose,
              Signature.ALG_HMAC_SHA_256,
              KMType.HMAC,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              (short) 0,
              false);
    }
    // Get the KeyObject from the operation and update the key with the secret key material.
    KMKeyObject keyObj = operation.getKeyObject();
    HMACKey key = (HMACKey) keyObj.keyObjectInst;
    key.setKey(secret, secretStart, secretLength);
    ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0);
    return operation;
  }

  private KMOperation createHmacSignerVerifier(
      short purpose, short digest, HMACKey hmacKey, boolean isTrustedConf) {
    if (digest != KMType.SHA2_256) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    KMOperation operation =
        poolMgr.getOperationImpl(
            purpose,
            Signature.ALG_HMAC_SHA_256,
            KMType.HMAC,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            (short) 0,
            isTrustedConf);
    // Get the KeyObject from the operation and update the key with the secret key material.
    KMKeyObject keyObj = operation.getKeyObject();
    HMACKey key = (HMACKey) keyObj.keyObjectInst;
    short len = hmacKey.getKey(tmpArray, (short) 0);
    key.setKey(tmpArray, (short) 0, len);
    ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0);
    return operation;
  }

  @Override
  public KMOperation getRkpOperation(
      byte purpose,
      byte alg,
      byte digest,
      byte padding,
      byte blockMode,
      KMKey keyPair,
      byte[] ivBuf,
      short ivStart,
      short ivLength,
      short macLength) {
    KMOperation opr = null;
    switch (alg) {
      case KMType.EC:
        // get EC private key buffer
        ECPrivateKey ecPrivKey =
            (ECPrivateKey) ((KMECDeviceUniqueKeyPair) keyPair).ecKeyPair.getPrivate();
        short ecPrivKeyLen = ecPrivKey.getS(tmpArray, (short) 0);
        opr = createEcSigner(digest, tmpArray, (short) 0, ecPrivKeyLen, true /* isRKP */);
        break;
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
    return opr;
  }

  @Override
  public KMOperation initSymmetricOperation(
      byte purpose,
      byte alg,
      byte digest,
      byte padding,
      byte blockMode,
      byte[] keyBuf,
      short keyStart,
      short keyLength,
      byte[] ivBuf,
      short ivStart,
      short ivLength,
      short macLength) {
    KMOperation opr = null;
    switch (alg) {
      case KMType.AES:
      case KMType.DES:
        // Convert macLength to bytes
        macLength = (short) (macLength / 8);
        opr =
            createSymmetricCipher(
                alg,
                purpose,
                macLength,
                blockMode,
                padding,
                keyBuf,
                keyStart,
                keyLength,
                ivBuf,
                ivStart,
                ivLength,
                false /* isRKP */);
        break;
      case KMType.HMAC:
        opr =
            createHmacSignerVerifier(
                purpose, digest, keyBuf, keyStart, keyLength, false /* isRKP */);
        break;
      default:
        CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
        break;
    }
    return opr;
  }

  @Override
  public KMOperation initSymmetricOperation(
      byte purpose,
      byte alg,
      byte digest,
      byte padding,
      byte blockMode,
      Object key,
      byte interfaceType,
      byte[] ivBuf,
      short ivStart,
      short ivLength,
      short macLength,
      boolean oneShot) {
    short keyLen = 0;
    globalOperation.setPurpose(purpose);
    globalOperation.setAlgorithmType(alg);
    globalOperation.setPaddingAlgorithm(padding);
    globalOperation.setBlockMode(blockMode);
    try {
      switch (interfaceType) {
        case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY:
          KMAESKey aesKey = (KMAESKey) key;
          keyLen = (short) (aesKey.aesKey.getSize() / 8);
          aesKey.aesKey.getKey(tmpArray, (short) 0);
          break;

        default:
          KMException.throwIt(KMError.INVALID_ARGUMENT);
      }

      switch (alg) {
        case KMType.HMAC:
          HMACKey hmackey = createHMACKey(tmpArray, (short) 0, keyLen);
          globalOperation.setSignature(hmacSignature);
          globalOperation.init(hmackey, digest, null, (short) 0, (short) 0);
          break;

        default:
          KMException.throwIt(KMError.INVALID_ARGUMENT);
      }
    } finally {
      clean();
    }
    return globalOperation;
  }

  @Override
  public KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey) {
    KMHmacKey key = (KMHmacKey) computedHmacKey;
    return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true);
  }

  public KMOperation createRsaSigner(
      short digest,
      short padding,
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] modBuffer,
      short modOff,
      short modLength) {
    byte alg = mapSignature256Alg(KMType.RSA, (byte) padding, (byte) digest);
    KMOperation operation =
        poolMgr.getOperationImpl(
            KMType.SIGN,
            alg,
            KMType.RSA,
            padding,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            secretLength,
            false);
    // Get the KeyObject from the operation and update the key with the secret key material.
    KMKeyObject keyObj = operation.getKeyObject();
    RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate();
    key.setExponent(secret, secretStart, secretLength);
    key.setModulus(modBuffer, modOff, modLength);
    ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0);
    return operation;
  }

  public KMOperation createRsaDecipher(
      short padding,
      short mgfDigest,
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] modBuffer,
      short modOff,
      short modLength) {
    byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0, (byte) mgfDigest);
    KMOperation operation =
        poolMgr.getOperationImpl(
            KMType.DECRYPT,
            cipherAlg,
            KMType.RSA,
            padding,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            secretLength,
            false);
    // Get the KeyObject from the operation and update the key with the secret key material.
    KMKeyObject keyObj = operation.getKeyObject();
    RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate();
    key.setExponent(secret, secretStart, secretLength);
    key.setModulus(modBuffer, modOff, modLength);
    ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0);
    return operation;
  }

  public KMOperation createEcSigner(
      short digest, byte[] secret, short secretStart, short secretLength, boolean isRkp) {
    KMOperation operation = null;
    byte alg = mapSignature256Alg(KMType.EC, (byte) 0, (byte) digest);
    if (isRkp) {
      operation =
          poolMgr.getRKpOperation(
              KMType.SIGN,
              Signature.ALG_ECDSA_SHA_256,
              KMType.EC,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE);
    } else {
      operation =
          poolMgr.getOperationImpl(
              KMType.SIGN,
              alg,
              KMType.EC,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              KMType.INVALID_VALUE,
              secretLength,
              false);
    }
    KMKeyObject keyObj = operation.getKeyObject();
    ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate();
    key.setS(secret, secretStart, secretLength);
    ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0);
    return operation;
  }

  public KMOperation createKeyAgreement(byte[] secret, short secretStart, short secretLength) {
    KMOperation operation =
        poolMgr.getOperationImpl(
            KMType.AGREE_KEY,
            KeyAgreement.ALG_EC_SVDP_DH_PLAIN,
            KMType.EC,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            (short) 0,
            false);
    KMKeyObject keyObj = operation.getKeyObject();
    ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate();
    key.setS(secret, secretStart, secretLength);
    ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0);
    return operation;
  }

  @Override
  public KMOperation initAsymmetricOperation(
      byte purpose,
      byte alg,
      byte padding,
      byte digest,
      byte mgfDigest,
      byte[] privKeyBuf,
      short privKeyStart,
      short privKeyLength,
      byte[] pubModBuf,
      short pubModStart,
      short pubModLength) {
    KMOperation opr = null;
    if (alg == KMType.RSA) {
      switch (purpose) {
        case KMType.SIGN:
          opr =
              createRsaSigner(
                  digest,
                  padding,
                  privKeyBuf,
                  privKeyStart,
                  privKeyLength,
                  pubModBuf,
                  pubModStart,
                  pubModLength);
          break;
        case KMType.DECRYPT:
          opr =
              createRsaDecipher(
                  padding,
                  mgfDigest,
                  privKeyBuf,
                  privKeyStart,
                  privKeyLength,
                  pubModBuf,
                  pubModStart,
                  pubModLength);
          break;
        default:
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          break;
      }
    } else if (alg == KMType.EC) {
      switch (purpose) {
        case KMType.SIGN:
          opr = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength, false);
          break;

        case KMType.AGREE_KEY:
          opr = createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength);
          break;
        default:
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          break;
      }
    } else {
      CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM);
    }
    return opr;
  }

  @Override
  public short cmacKDF(
      KMKey pSharedKey,
      byte[] label,
      short labelStart,
      short labelLen,
      byte[] context,
      short contextStart,
      short contextLength,
      byte[] keyBuf,
      short keyStart) {
    HMACKey key =
        cmacKdf(pSharedKey, label, labelStart, labelLen, context, contextStart, contextLength);
    return key.getKey(keyBuf, keyStart);
  }

  @Override
  public boolean isUpgrading() {
    return UpgradeManager.isUpgrading();
  }

  @Override
  public KMKey createMasterKey(KMKey masterKey, short keySizeBits) {
    try {
      if (masterKey == null) {
        AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false);
        masterKey = new KMAESKey(key);
      }
      short keyLen = (short) (keySizeBits / 8);
      Util.arrayFillNonAtomic(tmpArray, (short) 0, keyLen, (byte) 0);
      getTrueRandomNumber(tmpArray, (short) 0, keyLen);
      ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0);
      return (KMKey) masterKey;
    } finally {
      clean();
    }
  }

  @Override
  public KMKey createPreSharedKey(KMKey preSharedKey, byte[] keyData, short offset, short length) {
    short lengthInBits = (short) (length * 8);
    if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    if (preSharedKey == null) {
      HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, lengthInBits, false);
      preSharedKey = new KMHmacKey(key);
    }
    ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length);
    return (KMKey) preSharedKey;
  }

  @Override
  public KMKey createComputedHmacKey(
      KMKey computedHmacKey, byte[] keyData, short offset, short length) {
    if (length != COMPUTED_HMAC_KEY_SIZE) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    if (computedHmacKey == null) {
      HMACKey key =
          (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false);
      computedHmacKey = new KMHmacKey(key);
    }
    ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length);
    return (KMKey) computedHmacKey;
  }

  @Override
  public short ecSign256(
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] inputDataBuf,
      short inputDataStart,
      short inputDataLength,
      byte[] outputDataBuf,
      short outputDataStart) {

    ECPrivateKey key = (ECPrivateKey) ecKeyPair.getPrivate();
    key.setS(secret, secretStart, secretLength);

    Signature.OneShot signer = null;
    try {

      signer =
          Signature.OneShot.open(
              MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL);
      signer.init(key, Signature.MODE_SIGN);
      return signer.sign(
          inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart);
    } finally {
      if (signer != null) {
        signer.close();
      }
    }
  }

  @Override
  public short rsaSign256Pkcs1(
      byte[] secret,
      short secretStart,
      short secretLength,
      byte[] modBuf,
      short modStart,
      short modLength,
      byte[] inputDataBuf,
      short inputDataStart,
      short inputDataLength,
      byte[] outputDataBuf,
      short outputDataStart) {

    Signature.OneShot signer = null;
    try {

      signer =
          Signature.OneShot.open(
              MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_RSA, Cipher.PAD_PKCS1);

      RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate();
      key.setExponent(secret, secretStart, secretLength);
      key.setModulus(modBuf, modStart, modLength);

      signer.init(key, Signature.MODE_SIGN);
      return signer.sign(
          inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart);
    } finally {
      if (signer != null) {
        signer.close();
      }
    }
  }

  @Override
  public boolean isAttestationKeyProvisioned() {
    return false;
  }

  @Override
  public short getAttestationKeyAlgorithm() {
    return KMType.INVALID_VALUE;
  }

  @Override
  public short hkdf(
      byte[] ikm,
      short ikmOff,
      short ikmLen,
      byte[] salt,
      short saltOff,
      short saltLen,
      byte[] info,
      short infoOff,
      short infoLen,
      byte[] out,
      short outOff,
      short outLen) {
    // HMAC_extract
    hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, tmpArray, (short) 0);
    // HMAC_expand
    return hkdfExpand(tmpArray, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen);
  }

  private short hkdfExtract(
      byte[] ikm,
      short ikmOff,
      short ikmLen,
      byte[] salt,
      short saltOff,
      short saltLen,
      byte[] out,
      short off) {
    // https://tools.ietf.org/html/rfc5869#section-2.2
    HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen);
    hmacSignature.init(hmacKey, Signature.MODE_SIGN);
    return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off);
  }

  private short hkdfExpand(
      byte[] prk,
      short prkOff,
      short prkLen,
      byte[] info,
      short infoOff,
      short infoLen,
      byte[] out,
      short outOff,
      short outLen) {
    // https://tools.ietf.org/html/rfc5869#section-2.3
    short digestLen = (short) 32; // SHA256 digest length.
    // Calculate no of iterations N.
    short n = (short) ((short) (outLen + digestLen - 1) / digestLen);
    if (n > 255) {
      CryptoException.throwIt(CryptoException.ILLEGAL_VALUE);
    }
    HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen);
    Util.arrayFill(tmpArray, (short) 0, (short) 33, (byte) 0);
    short bytesCopied = 0;
    short len = 0;
    for (short i = 0; i < n; i++) {
      tmpArray[0]++;
      hmacSignature.init(hmacKey, Signature.MODE_SIGN);
      if (i != 0) {
        hmacSignature.update(tmpArray, (short) 1, (short) 32);
      }
      hmacSignature.update(info, infoOff, infoLen);
      len = hmacSignature.sign(tmpArray, (short) 0, (short) 1, tmpArray, (short) 1);
      if ((short) (bytesCopied + len) > outLen) {
        len = (short) (outLen - bytesCopied);
      }
      Util.arrayCopyNonAtomic(tmpArray, (short) 1, out, (short) (outOff + bytesCopied), len);
      bytesCopied += len;
    }
    return outLen;
  }

  @Override
  public short ecdhKeyAgreement(
      byte[] privKey,
      short privKeyOff,
      short privKeyLen,
      byte[] publicKey,
      short publicKeyOff,
      short publicKeyLen,
      byte[] secret,
      short secretOff) {
    keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen));
    return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff);
  }

  @Override
  public boolean ecVerify256(
      byte[] pubKey,
      short pubKeyOffset,
      short pubKeyLen,
      byte[] inputDataBuf,
      short inputDataStart,
      short inputDataLength,
      byte[] signatureDataBuf,
      short signatureDataStart,
      short signatureDataLen) {
    Signature.OneShot signer = null;
    try {
      signer =
          Signature.OneShot.open(
              MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL);
      ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic();
      key.setW(pubKey, pubKeyOffset, pubKeyLen);
      signer.init(key, Signature.MODE_VERIFY);
      return signer.verify(
          inputDataBuf,
          inputDataStart,
          inputDataLength,
          signatureDataBuf,
          signatureDataStart,
          (short) (signatureDataBuf[(short) (signatureDataStart + 1)] + 2));
    } finally {
      if (signer != null) {
        signer.close();
      }
    }
  }

  @Override
  public short signWithDeviceUniqueKey(
      KMKey ecPrivKey,
      byte[] inputDataBuf,
      short inputDataStart,
      short inputDataLength,
      byte[] outputDataBuf,
      short outputDataStart) {
    Signature.OneShot signer = null;
    try {
      signer =
          Signature.OneShot.open(
              MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL);
      signer.init(
          ((KMECDeviceUniqueKeyPair) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN);
      return signer.sign(
          inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart);
    } finally {
      if (signer != null) {
        signer.close();
      }
    }
  }

  @Override
  public KMKey createRkpDeviceUniqueKeyPair(
      KMKey key,
      byte[] pubKey,
      short pubKeyOff,
      short pubKeyLen,
      byte[] privKey,
      short privKeyOff,
      short privKeyLen) {
    if (key == null) {
      KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256);
      poolMgr.initECKey(ecKeyPair);
      key = new KMECDeviceUniqueKeyPair(ecKeyPair);
    }
    ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPrivate();
    ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPublic();
    ecKeyPair.setS(privKey, privKeyOff, privKeyLen);
    ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen);
    return (KMKey) key;
  }

  @Override
  public KMKey createRkpMacKey(KMKey rkpMacKey, byte[] keyData, short offset, short length) {
    if (rkpMacKey == null) {
      HMACKey key =
          (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false);
      rkpMacKey = new KMHmacKey(key);
    }
    ((KMHmacKey) rkpMacKey).hmacKey.setKey(keyData, offset, length);
    return rkpMacKey;
  }

  @Override
  public short messageDigest256(
      byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) {
    MessageDigest.OneShot mDigest = null;
    short len = 0;
    try {
      mDigest = MessageDigest.OneShot.open(MessageDigest.ALG_SHA_256);
      len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset);
    } finally {
      if (mDigest != null) {
        mDigest.close();
        mDigest = null;
      }
    }
    return len;
  }

  public boolean isPowerReset() {
    boolean flag = false;
    if (resetFlag[0] == POWER_RESET_TRUE) {
      resetFlag[0] = POWER_RESET_FALSE;
      flag = true;
      if (poolMgr != null) {
        poolMgr.powerReset();
      }
    }
    return flag;
  }

  @Override
  public void onSave(Element element, byte interfaceType, Object object) {
    element.write(interfaceType);
    if (object == null) {
      element.write(null);
      return;
    }
    switch (interfaceType) {
      case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY:
        KMAESKey.onSave(element, (KMAESKey) object);
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY:
        KMHmacKey.onSave(element, (KMHmacKey) object);
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR:
        KMECDeviceUniqueKeyPair.onSave(element, (KMECDeviceUniqueKeyPair) object);
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY:
        KMHmacKey.onSave(element, (KMHmacKey) object);
        break;
      default:
        ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
  }

  @Override
  public Object onRestore(Element element) {
    if (element == null) {
      return null;
    }
    byte interfaceType = element.readByte();
    switch (interfaceType) {
      case KMDataStoreConstants.INTERFACE_TYPE_COMPUTED_HMAC_KEY:
        return KMHmacKey.onRestore((HMACKey) element.readObject());
      case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY:
        return KMAESKey.onRestore((AESKey) element.readObject());
      case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY:
        return KMHmacKey.onRestore((HMACKey) element.readObject());
      case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR:
        return KMECDeviceUniqueKeyPair.onRestore((KeyPair) element.readObject());
      case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY:
        return KMHmacKey.onRestore((HMACKey) element.readObject());
      default:
        ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    return null;
  }

  @Override
  public short getBackupPrimitiveByteCount(byte interfaceType) {
    short primitiveCount = 1; // interface type
    switch (interfaceType) {
      case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY:
        primitiveCount += KMAESKey.getBackupPrimitiveByteCount();
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY:
        primitiveCount += KMHmacKey.getBackupPrimitiveByteCount();
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR:
        primitiveCount += KMECDeviceUniqueKeyPair.getBackupPrimitiveByteCount();
        break;
      case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY:
        primitiveCount += KMHmacKey.getBackupPrimitiveByteCount();
        break;
      default:
        ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    return primitiveCount;
  }

  @Override
  public short getBackupObjectCount(byte interfaceType) {
    switch (interfaceType) {
      case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY:
        return KMAESKey.getBackupObjectCount();
      case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY:
        return KMHmacKey.getBackupObjectCount();
      case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR:
        return KMECDeviceUniqueKeyPair.getBackupObjectCount();
      case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY:
        return KMHmacKey.getBackupObjectCount();
      default:
        ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
    }
    return 0;
  }

  @Override
  public boolean isBootSignalEventSupported() {
    return false;
  }

  @Override
  public boolean isDeviceRebooted() {
    return false;
  }

  @Override
  public void clearDeviceBooted(boolean resetBootFlag) {
    // To be filled
  }
}
