/*
 * 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 com.android.javacard.seprovider.KMAttestationCert;
import com.android.javacard.seprovider.KMDataStoreConstants;
import com.android.javacard.seprovider.KMException;
import com.android.javacard.seprovider.KMKey;
import com.android.javacard.seprovider.KMOperation;
import com.android.javacard.seprovider.KMSEProvider;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.AppletEvent;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.CryptoException;
import javacardx.apdu.ExtendedLength;

/**
 * KMKeymasterApplet implements the javacard applet. It creates an instance of the KMRepository and
 * other install time objects. It also implements the keymaster state machine and handles javacard
 * applet life cycle events.
 */
public abstract class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength {

  // Constants.
  // Represents RSA_PUBLIC_EXPONENT value 65537.
  public static final byte[] F4 = {0x01, 0x00, 0x01};
  // Block size of AES algorithm.
  public static final byte AES_BLOCK_SIZE = 16;
  // Block size of DES algorithm.
  public static final byte DES_BLOCK_SIZE = 8;
  // The Key size in bits for the master key.
  public static final short MASTER_KEY_SIZE = 128;
  // The Key size of the transport key used in importWrappedKey.
  public static final byte WRAPPING_KEY_SIZE = 32;
  // The maximum allowed simultaneous operations.
  public static final byte MAX_OPERATIONS_COUNT = 4;
  // The size of the verified boot key in ROT.
  public static final byte VERIFIED_BOOT_KEY_SIZE = 32;
  // The size of the verified boot hash in ROT.
  public static final byte VERIFIED_BOOT_HASH_SIZE = 32;
  // The security level of TEE.
  public static final byte TRUSTED_ENVIRONMENT = 1;
  // "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
  };
  // The ckdfLabel "KeymasterSharedMac" in hex.
  public static final byte[] ckdfLabel = {
    0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D,
    0x61, 0x63
  };
  // The "Auth Verification" string in hex.
  public static final byte[] authVerification = {
    0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F,
    0x6E
  };
  // The "confirmation token" string in hex.
  public static final byte[] confirmationToken = {
    0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B,
    0x65, 0x6E
  };
  // The maximum buffer size for the encoded COSE structures.
  public static final short MAX_COSE_BUF_SIZE = (short) 512;
  // Maximum allowed buffer size for to encode the key parameters
  // which is used while creating mac for key parameters.
  public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K
  // Temporary variables array size to store intermediary results.
  public static final byte TMP_VARIABLE_ARRAY_SIZE = 5;
  // Data Dictionary items
  // Maximum Dictionary size.
  public static final byte DATA_ARRAY_SIZE = 39;
  // Below are the offsets of the data dictionary items.
  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 NOT_USED = 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;
  public static final byte SIGNATURE = 28;
  public static final byte ATTEST_KEY_BLOB = 29;
  public static final byte ATTEST_KEY_PARAMS = 30;
  public static final byte ATTEST_KEY_ISSUER = 31;
  public static final byte CERTIFICATE = 32;
  public static final byte PLAIN_SECRET = 33;
  public static final byte TEE_PARAMETERS = 34;
  public static final byte SB_PARAMETERS = 35;
  public static final byte CONFIRMATION_TOKEN = 36;
  public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37;
  public static final byte CUSTOM_TAGS = 38;
  // Below are the Keyblob offsets.
  public static final byte KEY_BLOB_VERSION_OFFSET = 0;
  public static final byte KEY_BLOB_SECRET = 1;
  public static final byte KEY_BLOB_NONCE = 2;
  public static final byte KEY_BLOB_AUTH_TAG = 3;
  public static final byte KEY_BLOB_PARAMS = 4;
  public static final byte KEY_BLOB_CUSTOM_TAGS = 5;
  public static final byte KEY_BLOB_PUB_KEY = 6;
  // AES GCM Auth tag length to be used while encrypting or decrypting the KeyBlob.
  public static final byte AES_GCM_AUTH_TAG_LENGTH = 16;
  // AES GCM nonce length to be used while encrypting or decrypting the KeyBlob.
  public static final byte AES_GCM_NONCE_LENGTH = 12;
  // KEYBLOB_CURRENT_VERSION goes into KeyBlob and will affect all
  // the KeyBlobs if it is changed. please increment this
  // version number whenever you change anything related to
  // KeyBlob (structure, encryption algorithm etc).
  public static final byte KEYBLOB_CURRENT_VERSION = 3;
  // KeyBlob Verion 1 constant.
  public static final byte KEYBLOB_VERSION_1 = 1;
  // Array sizes of KeyBlob under different versions.
  // The array size of a Symmetric key's KeyBlob for Version2 and Version3
  public static final byte SYM_KEY_BLOB_SIZE_V2_V3 = 6;
  // The array size of a Asymmetric key's KeyBlob for Version2 and Version3
  public static final byte ASYM_KEY_BLOB_SIZE_V2_V3 = 7;
  // The array size of a Symmetric key's KeyBlob for Version1
  public static final byte SYM_KEY_BLOB_SIZE_V1 = 5;
  // The array size of a Asymmetric key's KeyBlob for Version1
  public static final byte ASYM_KEY_BLOB_SIZE_V1 = 6;
  // The array size of a Symmetric key's KeyBlob for Version0
  public static final byte SYM_KEY_BLOB_SIZE_V0 = 4;
  // The array size of a Asymmetric key's KeyBlob for Version0
  public static final byte ASYM_KEY_BLOB_SIZE_V0 = 5;
  // Key type constants
  // Represents the type of the Symmetric key.
  public static final byte SYM_KEY_TYPE = 0;
  // Represents the type of the Asymmetric key.
  public static final byte ASYM_KEY_TYPE = 1;
  // SHA-256 Digest length in bits
  public static final short SHA256_DIGEST_LEN_BITS = 256;
  // Minimum HMAC length in bits
  public static final byte MIN_HMAC_LENGTH_BITS = 64;
  // Below are the constants for provision reporting status
  public static final short NOT_PROVISIONED = 0x0000;
  public static final short PROVISION_STATUS_ATTESTATION_KEY = 0x0001;
  public static final short PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x0002;
  public static final short PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x0004;
  public static final short PROVISION_STATUS_ATTEST_IDS = 0x0008;
  public static final short PROVISION_STATUS_PRESHARED_SECRET = 0x0010;
  public static final short PROVISION_STATUS_PROVISIONING_LOCKED = 0x0020;
  public static final short PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR = 0x0040;
  public static final short PROVISION_STATUS_UDS_CERT_CHAIN = 0x0080;
  public static final short PROVISION_STATUS_SE_LOCKED = 0x0100;
  public static final short PROVISION_STATUS_OEM_PUBLIC_KEY = 0x0200;
  public static final short PROVISION_STATUS_SECURE_BOOT_MODE = 0x0400;

  // OEM lock / unlock verification constants.
  // This is the verification label to authenticate the OEM to lock the provisioning for the
  // OEM provision commands.
  protected static final byte[] OEM_LOCK_PROVISION_VERIFICATION_LABEL = { // "OEM Provisioning Lock"
    0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67,
    0x20, 0x4c, 0x6f, 0x63, 0x6b
  };
  // This is the verification label to authenticate the OEM to unlock the provisioning for the
  // OEM provision commands.
  protected static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA"
    0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41
  };
  // The maximum size of the seed allowed for the RNG entropy
  protected static final short MAX_SEED_SIZE = 2048;
  // The maximum size of the certificate returned by the generate key command.
  protected static final short MAX_CERT_SIZE = 3000;
  // The maximum size of the encoded key characteristics in CBOR.
  protected static final short MAX_KEY_CHARS_SIZE = 512;
  // The maximum size of the serialized KeyBlob.
  protected static final short MAX_KEYBLOB_SIZE = 1024;
  // The maximum size of the Auth data which is used while encrypting/decrypting the KeyBlob.
  private static final short MAX_AUTH_DATA_SIZE = (short) 512;
  // The minimum bits in length for AES-GCM tag.
  private static final byte MIN_GCM_TAG_LENGTH_BITS = (short) 96;
  // The maximum bits in length for AES-GCM tag.
  private static final short MAX_GCM_TAG_LENGTH_BITS = (short) 128;
  // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys
  private static final byte[] defaultSubject = {
    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
  };
  // Constant for Dec 31, 9999 in milliseconds in hex.
  private static final byte[] dec319999Ms = {
    (byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18
  };
  // Dec 31, 9999 represented in Generalized time format YYYYMMDDhhmmssZ.
  // "99991231235959Z" in hex. Refer RFC 5280 section 4.1.2.5.2
  private static final byte[] dec319999 = {
    0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a,
  };
  // Jan 01, 1970 represented in UTC time format YYMMDDhhmmssZ.
  // "700101000000Z" in hex. Refer RFC 5280 section 4.1.2.5.1
  private static final byte[] jan01970 = {
    0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a,
  };
  // The KeyMint name "JavacardKeymintDevice" returned from getHwInfo.
  private static final byte[] JavacardKeymintDevice = {
    0x4a, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0x44,
    0x65, 0x76, 0x69, 0x63, 0x65,
  };
  // The KeyMint author name "Google" returned from getHwInfo.
  public static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65};
  // Attestation ID tags to be included in attestation record.
  private static final short[] attTags = {
    KMType.ATTESTATION_ID_BRAND,
    KMType.ATTESTATION_ID_DEVICE,
    KMType.ATTESTATION_ID_IMEI,
    KMType.ATTESTATION_ID_SECOND_IMEI,
    KMType.ATTESTATION_ID_MANUFACTURER,
    KMType.ATTESTATION_ID_MEID,
    KMType.ATTESTATION_ID_MODEL,
    KMType.ATTESTATION_ID_PRODUCT,
    KMType.ATTESTATION_ID_SERIAL
  };
  // Below are the constants of instructions in APDU command header.
  // Top 32 commands are reserved for provisioning.
  private static final byte KEYMINT_CMD_APDU_START = 0x20;
  // RKP
  public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; // 0x3B
  public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; // 0x3C
  public static final byte INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29; // 0x3D
  public static final byte INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30; // 0x3E
  // Constant
  public static final byte INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31; // 0x3F
  public static final byte INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32; // 0x40
  public static final byte INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33; // 0x41
  public static final byte INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34; // 0x42
  public static final byte INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35; // 0x43
  public static final byte INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36; // 0x44
  private static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; // 0x21
  private static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; // 0x22
  private static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; // 0x23
  private static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; // 0x24
  private static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; // 0x25
  private static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; // 0x26
  private static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; // 0x27
  private static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; // 0x28
  private static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; // 0x29
  private static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; // 0x2A
  private static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; // 0x2B
  private static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; // 0x2C
  private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; // 0x2D
  private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; // 0x2E
  private static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; // 0x2F
  private static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; // 0x30
  private static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; // 0x31
  private static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; // 0x32
  private static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; // 0x33
  private static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20; // 0x34
  private static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; // 0x35
  private static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; // 0x36
  private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; // 0x37
  private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; // 0x38
  private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; // 0x39
  private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; // 0x3A
  // The instructions from 0x43 to 0x4C will be reserved for KeyMint 1.0 for any future use.
  // KeyMint 2.0 Instructions
  private static final byte INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45; // 0x4D
  private static final byte INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46; // 0x4E
  private static final byte INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47; // 0x4F
  public static final short INS_SET_ADDITIONAL_ATTESTATION_INFO =
      KEYMINT_CMD_APDU_START + 48; // 0x50
  private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 49; // 0x51
  private static final byte INS_END_KM_CMD = 0x7F;
  // Instruction values from 0xCD to 0xFF are completely reserved for Vendors to use and
  // will never be used by the base line code in future.
  private static final byte INS_KM_VENDOR_START_CMD = (byte) 0xCD;
  private static final byte INS_KM_VENDOR_END_CMD = (byte) 0xFF;
  // Index in apduFlagsStatus[] to check if instruction command is case 4 type in the Apdu
  protected static final byte APDU_CASE4_COMMAND_STATUS_INDEX = 0;
  // Index in apduFlagsStatus[] to check if Apdu setIncomingAndReceive function is called
  protected static final byte APDU_INCOMING_AND_RECEIVE_STATUS_INDEX = 1;
  // The maximum buffer size of combined seed and nonce.
  private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64;
  // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands.
  protected static KMRemotelyProvisionedComponentDevice rkp;
  // Instance of Cbor encoder.
  protected static KMEncoder encoder;
  // Instance of Cbor decoder.
  protected static KMDecoder decoder;
  // Instance of KMRepository class for memory management.
  protected static KMRepository repository;
  // Instance of KMSEProvider for doing crypto operations.
  protected static KMSEProvider seProvider;
  // Holds the instance of KMOperationStates. A maximum of 4 instances of KMOperatioState is
  // allowed.
  protected static KMOperationState[] opTable;
  // Instance of KMKeymintDataStore which helps to store and retrieve the data.
  protected static KMKeymintDataStore kmDataStore;

  // Short array used to store the temporary results.
  protected static short[] tmpVariables;
  // Short array used to hold the dictionary items.
  protected static short[] data;
  // Buffer to store the transportKey which is used in the import wrapped key. Import wrapped
  // key is divided into two stages 1. BEGIN_IMPORT_WRAPPED_KEY 2. FINISH_IMPORT_WRAPPED_KEY.
  // The transportKey is retrieved and stored in this buffer at stage 1) and is later used in
  // stage 2).
  protected static byte[] wrappingKey;
  // Transient byte array used to store the flags if APDU command type is of case 4 and if
  // APDU setIncomingAndReceive() function is called or not.
  protected static byte[] apduStatusFlags;

  /** Registers this applet. */
  protected KMKeymasterApplet(KMSEProvider seImpl) {
    seProvider = seImpl;
    boolean isUpgrading = seProvider.isUpgrading();
    repository = new KMRepository(isUpgrading);
    encoder = new KMEncoder();
    decoder = new KMDecoder();
    kmDataStore = new KMKeymintDataStore(seProvider, repository);
    data = JCSystem.makeTransientShortArray(DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT);
    tmpVariables =
        JCSystem.makeTransientShortArray(TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT);
    wrappingKey =
        JCSystem.makeTransientByteArray((short) (WRAPPING_KEY_SIZE + 1), JCSystem.CLEAR_ON_RESET);
    resetWrappingKey();
    apduStatusFlags = JCSystem.makeTransientByteArray((short) 2, JCSystem.CLEAR_ON_RESET);
    opTable = new KMOperationState[MAX_OPERATIONS_COUNT];
    short index = 0;
    while (index < MAX_OPERATIONS_COUNT) {
      opTable[index] = new KMOperationState();
      index++;
    }
    KMType.initialize();
    if (!isUpgrading) {
      // For keyMint 3.0 and above installation, set ignore second Imei flag to false.
      kmDataStore.ignoreSecondImei = false;
      kmDataStore.createMasterKey(MASTER_KEY_SIZE);
    }
    // initialize default values
    initHmacNonceAndSeed();
    rkp =
        new KMRemotelyProvisionedComponentDevice(
            encoder, decoder, repository, seProvider, kmDataStore);
  }

  /** Sends a response, may be extended response, as requested by the command. */
  public static void sendOutgoing(APDU apdu, short resp) {
    // TODO handle the extended buffer stuff. We can reuse this.
    short bufferStartOffset = repository.allocAvailableMemory();
    byte[] buffer = repository.getHeap();
    // TODO we can change the following to incremental send.
    short bufferLength =
        encoder.encode(resp, buffer, bufferStartOffset, repository.getHeapReclaimIndex());
    if (((short) (bufferLength + bufferStartOffset)) > ((short) repository.getHeap().length)) {
      ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
    }

    /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must
     * be invoked prior to calling setOutgoing(). Otherwise, erroneous
     * behavior may result
     * */
    if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1
        && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0
        && APDU.getProtocol() == APDU.PROTOCOL_T0) {
      apdu.setIncomingAndReceive();
    }
    // Send data
    apdu.setOutgoing();
    apdu.setOutgoingLength(bufferLength);
    apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength);
  }

  /** Receives data, which can be extended data, as requested by the command instance. */
  public static short receiveIncoming(APDU apdu, short reqExp) {
    byte[] srcBuffer = apdu.getBuffer();
    short recvLen = apdu.setIncomingAndReceive();
    short srcOffset = apdu.getOffsetCdata();
    apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1;
    // TODO add logic to handle the extended length buffer. In this case the memory can be reused
    //  from extended buffer.
    short bufferLength = apdu.getIncomingLength();
    short bufferStartOffset = repository.allocReclaimableMemory(bufferLength);
    short index = bufferStartOffset;
    byte[] buffer = repository.getHeap();
    while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) {
      Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen);
      index += recvLen;
      recvLen = apdu.receiveBytes(srcOffset);
    }
    short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength);
    repository.reclaimMemory(bufferLength);
    return req;
  }

  private static short createKeyBlobInstance(byte keyType) {
    short arrayLen = 0;
    switch (keyType) {
      case ASYM_KEY_TYPE:
        arrayLen = ASYM_KEY_BLOB_SIZE_V2_V3;
        break;
      case SYM_KEY_TYPE:
        arrayLen = SYM_KEY_BLOB_SIZE_V2_V3;
        break;
      default:
        KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
    }
    return KMArray.instance(arrayLen);
  }

  private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) {
    short index = 0;
    short arr = KMKeyParameters.cast(params).getVals();
    short len = KMArray.cast(arr).length();
    short tag;
    while (index < len) {
      tag = KMArray.cast(arr).get(index);
      cert.extensionTag(tag, hwEnforced);
      index++;
    }
  }

  private static void setUniqueId(KMAttestationCert cert, short attAppId, byte[] scratchPad) {
    if (!KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)) {
      return;
    }
    // temporal count T
    short time =
        KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[KEY_PARAMETERS]);
    if (time == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    time = KMIntegerTag.cast(time).getValue();

    // Reset After Rotation R - it will be part of HW Enforced key
    // characteristics
    byte resetAfterRotation = 0;
    if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION)) {
      resetAfterRotation = 0x01;
    }

    cert.makeUniqueId(
        scratchPad,
        (short) 0,
        KMInteger.cast(time).getBuffer(),
        KMInteger.cast(time).getStartOff(),
        KMInteger.cast(time).length(),
        KMByteBlob.cast(attAppId).getBuffer(),
        KMByteBlob.cast(attAppId).getStartOff(),
        KMByteBlob.cast(attAppId).length(),
        resetAfterRotation,
        kmDataStore.getMasterKey());
  }

  private static void validateRSAKey(byte[] scratchPad) {
    // Read key size
    if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
    if (!KMTag.isValidPublicExponent(data[KEY_PARAMETERS])) {
      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
    short[] lengths = tmpVariables;
    data[PUB_KEY] = KMByteBlob.instance((short) 256);
    data[SECRET] = KMByteBlob.instance((short) 256);
    seProvider.createAsymmetricKey(
        KMType.RSA,
        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(),
        lengths);

    data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
  }

  private static void validateAESKey() {
    // Read key size
    if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
    // Read Block mode - array of byte values
    if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE)) {
      short blockModes =
          KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]);
      // If it is a GCM mode
      if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)) {
        // Min mac length must be present
        KMTag.assertPresence(
            data[KEY_PARAMETERS],
            KMType.UINT_TAG,
            KMType.MIN_MAC_LENGTH,
            KMError.MISSING_MIN_MAC_LENGTH);
        short macLength =
            KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]);
        macLength = KMIntegerTag.cast(macLength).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(macLength).getSignificantShort() != 0
            || KMInteger.cast(macLength).getShort() > 128
            || KMInteger.cast(macLength).getShort() < 96
            || (KMInteger.cast(macLength).getShort() % 8) != 0) {
          KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH);
        }
      }
    }
  }

  private static void generateAESKey(byte[] scratchPad) {
    validateAESKey();
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    short len = seProvider.createSymmetricKey(KMType.AES, keysize, scratchPad, (short) 0);
    data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len);
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  private static void validateECKeys() {
    // Read key size
    short ecCurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]);
    /* In KeyMint 2.0, If EC_CURVE not provided, generateKey
     * must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE.
     */
    if (ecCurve != KMType.P_256) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
    short ecKeySize = KMEnumTag.getValue(KMType.KEYSIZE, data[KEY_PARAMETERS]);
    if ((ecKeySize != KMType.INVALID_VALUE) && !KMTag.isValidKeySize(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
  }

  private static void generateECKeys(byte[] scratchPad) {
    validateECKeys();
    short[] lengths = tmpVariables;
    seProvider.createAsymmetricKey(
        KMType.EC,
        scratchPad,
        (short) 0,
        (short) 128,
        scratchPad,
        (short) 128,
        (short) 128,
        lengths);
    data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]);
    data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]);
    data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
  }

  private static void validateTDESKey() {
    if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
    // Read Minimum Mac length - it must not be present
    KMTag.assertAbsence(
        data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG);
  }

  private static void generateTDESKey(byte[] scratchPad) {
    validateTDESKey();
    short len = seProvider.createSymmetricKey(KMType.DES, (short) 168, scratchPad, (short) 0);
    data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len);
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  private static void validateHmacKey() {
    // If params does not contain any digest throw unsupported digest error.
    KMTag.assertPresence(
        data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST, 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.SHA2_256, data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
    }
    // Read Minimum Mac length
    KMTag.assertPresence(
        data[KEY_PARAMETERS],
        KMType.UINT_TAG,
        KMType.MIN_MAC_LENGTH,
        KMError.MISSING_MIN_MAC_LENGTH);
    short minMacLength =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]);

    if (((short) (minMacLength % 8) != 0)
        || minMacLength < MIN_HMAC_LENGTH_BITS
        || minMacLength > SHA256_DIGEST_LEN_BITS) {
      KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH);
    }
    // Read Keysize
    if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
  }

  private static void generateHmacKey(byte[] scratchPad) {
    validateHmacKey();
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    // generate HMAC Key
    short len = seProvider.createSymmetricKey(KMType.HMAC, keysize, scratchPad, (short) 0);
    data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len);
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  // This function is only called from processUpgradeKey command.
  // 1. Update the latest values of OSVersion, OSPatch, VendorPatch and BootPatch in the
  //    KeyBlob's KeyCharacteristics.
  // 2. Re-create KeyBlob's KeyCharacteristics from HW_PARAMS to make sure we don't miss
  //    anything which happens in these functions makeSbEnforced and makeTeeEnforced in
  //    the future. Like validations.
  // 3. No need to create Keystore Enforced list here as it is not required to be included in
  //    the KeyBlob's KeyCharacteristics.
  // 4. No need to create KeyCharacteristics as upgradeKey does not require to return any
  //    KeyCharacteristics back.
  private static void upgradeKeyBlobKeyCharacteristics(short hwParams, byte[] scratchPad) {
    short osVersion = kmDataStore.getOsVersion();
    short osPatch = kmDataStore.getOsPatch();
    short vendorPatch = kmDataStore.getVendorPatchLevel();
    short bootPatch = kmDataStore.getBootPatchLevel();
    data[SB_PARAMETERS] =
        KMKeyParameters.makeSbEnforced(
            hwParams, (byte) data[ORIGIN], osVersion, osPatch, vendorPatch, bootPatch, scratchPad);
    data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(hwParams, scratchPad);
    data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]);
  }

  private static void makeKeyCharacteristics(byte[] scratchPad) {
    short osVersion = kmDataStore.getOsVersion();
    short osPatch = kmDataStore.getOsPatch();
    short vendorPatch = kmDataStore.getVendorPatchLevel();
    short bootPatch = kmDataStore.getBootPatchLevel();
    data[SB_PARAMETERS] =
        KMKeyParameters.makeSbEnforced(
            data[KEY_PARAMETERS],
            (byte) data[ORIGIN],
            osVersion,
            osPatch,
            vendorPatch,
            bootPatch,
            scratchPad);
    data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(data[KEY_PARAMETERS], scratchPad);
    data[SW_PARAMETERS] = KMKeyParameters.makeKeystoreEnforced(data[KEY_PARAMETERS], scratchPad);
    data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]);
    data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance();
    KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setStrongboxEnforced(data[SB_PARAMETERS]);
    KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setKeystoreEnforced(data[SW_PARAMETERS]);
    KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]);
  }

  private static void createEncryptedKeyBlob(byte[] scratchPad) {
    // make root of trust blob
    data[ROT] = readROT(scratchPad, KEYBLOB_CURRENT_VERSION);
    if (data[ROT] == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    // make hidden key params list
    data[HIDDEN_PARAMETERS] =
        KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad);
    data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_16(KEYBLOB_CURRENT_VERSION);
    // create custom tags
    data[CUSTOM_TAGS] = KMKeyParameters.makeCustomTags(data[HW_PARAMETERS], 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_VERSION_OFFSET, data[KEY_BLOB_VERSION_DATA_OFFSET]);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_CUSTOM_TAGS, data[CUSTOM_TAGS]);

    short tempChar = KMKeyCharacteristics.instance();
    short emptyParam = KMArray.instance((short) 0);
    emptyParam = KMKeyParameters.instance(emptyParam);
    KMKeyCharacteristics.cast(tempChar).setStrongboxEnforced(data[SB_PARAMETERS]);
    KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam);
    KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar);
  }

  // Read RoT
  public static short readROT(byte[] scratchPad, short version) {
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
    short len = kmDataStore.getBootKey(scratchPad, (short) 0);
    // As per IKeyMintDevice.aidl specification The root of trust
    // consists of verifyBootKey, boot state and device locked.
    if (version <= KEYBLOB_VERSION_1) {
      // To parse old keyblobs verified boot hash is included in
      // the root of trust.
      len += kmDataStore.getVerifiedBootHash(scratchPad, (short) len);
    }
    short bootState = kmDataStore.getBootState();
    len = Util.setShort(scratchPad, len, bootState);
    if (kmDataStore.isDeviceBootLocked()) {
      scratchPad[len] = (byte) 1;
    } else {
      scratchPad[len] = (byte) 0;
    }
    len++;
    return KMByteBlob.instance(scratchPad, (short) 0, len);
  }

  private static void encryptSecret(byte[] scratchPad) {
    // make nonce
    data[NONCE] = KMByteBlob.instance(AES_GCM_NONCE_LENGTH);
    data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH);
    seProvider.newRandomNumber(
        KMByteBlob.cast(data[NONCE]).getBuffer(),
        KMByteBlob.cast(data[NONCE]).getStartOff(),
        KMByteBlob.cast(data[NONCE]).length());
    // derive master key - stored in derivedKey
    short len = deriveKey(scratchPad);
    len =
        seProvider.aesGCMEncrypt(
            KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(),
            KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(),
            KMByteBlob.cast(data[DERIVED_KEY]).length(),
            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(),
            null,
            (short) 0,
            (short) 0,
            KMByteBlob.cast(data[AUTH_TAG]).getBuffer(),
            KMByteBlob.cast(data[AUTH_TAG]).getStartOff(),
            KMByteBlob.cast(data[AUTH_TAG]).length());

    if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len);
  }

  private static byte getKeyType(short hardwareParams) {
    short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hardwareParams);
    if (KMEnumTag.cast(alg).getValue() == KMType.RSA
        || KMEnumTag.cast(alg).getValue() == KMType.EC) {
      return ASYM_KEY_TYPE;
    }
    return SYM_KEY_TYPE;
  }

  private static void makeAuthData(short version, byte[] scratchPad) {
    // For KeyBlob V2: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and
    // PUB_KEY.
    // For KeyBlob V1: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, VERSION and PUB_KEY.
    // For KeyBlob V0: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS and PUB_KEY.
    // VERSION is included only for KeyBlobs having version >= 1.
    // PUB_KEY is included for only ASYMMETRIC KeyBlobs.
    short index = 0;
    short numParams = 0;
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0);
    byte keyType = getKeyType(data[HW_PARAMETERS]);
    // Copy the relevant parameters in the scratchPad in the order
    // 1. HW_PARAMETERS
    // 2. HIDDEN_PARAMETERS
    // 3. VERSION ( Only Version >= 1)
    // 4. PUB_KEY ( Only for Asymmetric Keys)
    switch (version) {
      case (short) 0:
        numParams = 2;
        Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals());
        Util.setShort(
            scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals());
        // For Asymmetric Keys include the PUB_KEY.
        if (keyType == ASYM_KEY_TYPE) {
          numParams = 3;
          Util.setShort(scratchPad, (short) 4, data[PUB_KEY]);
        }
        break;
      case (short) 1:
        numParams = 3;
        Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals());
        Util.setShort(
            scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals());
        Util.setShort(scratchPad, (short) 4, data[KEY_BLOB_VERSION_DATA_OFFSET]);
        // For Asymmetric Keys include the PUB_KEY.
        if (keyType == ASYM_KEY_TYPE) {
          numParams = 4;
          Util.setShort(scratchPad, (short) 6, data[PUB_KEY]);
        }
        break;
      case (short) 2:
        numParams = 4;
        Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals());
        Util.setShort(
            scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals());
        Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals());
        Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]);
        // For Asymmetric Keys include the PUB_KEY.
        if (keyType == ASYM_KEY_TYPE) {
          numParams = 5;
          Util.setShort(scratchPad, (short) 8, data[PUB_KEY]);
        }
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE);
    index = 0;
    short len = 0;
    Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0);
    while (index < numParams) {
      short tag = Util.getShort(scratchPad, (short) (index * 2));
      len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32), prevReclaimIndex);
      Util.arrayCopyNonAtomic(
          repository.getHeap(),
          authIndex,
          repository.getHeap(),
          (short) (authIndex + len + 32),
          (short) 32);
      len =
          seProvider.messageDigest256(
              repository.getHeap(),
              (short) (authIndex + 32),
              (short) (len + 32),
              repository.getHeap(),
              authIndex);
      if (len != 32) {
        KMException.throwIt(KMError.UNKNOWN_ERROR);
      }
      index++;
    }
    short authDataIndex = repository.alloc(len);
    Util.arrayCopyNonAtomic(
        repository.getHeap(), authIndex, repository.getHeap(), authDataIndex, len);
    repository.reclaimMemory(MAX_AUTH_DATA_SIZE);
    data[AUTH_DATA] = authDataIndex;
    data[AUTH_DATA_LENGTH] = len;
  }

  private static short deriveKeyForOldKeyBlobs(byte[] scratchPad) {
    // KeyDerivation:
    // 1. Do HMAC Sign, Auth data.
    // 2. HMAC Sign generates an output of 32 bytes length.
    // Consume only first 16 bytes as derived key.
    // Hmac sign.
    short len =
        seProvider.hmacKDF(
            kmDataStore.getMasterKey(),
            repository.getHeap(),
            data[AUTH_DATA],
            data[AUTH_DATA_LENGTH],
            scratchPad,
            (short) 0);
    if (len < 16) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    len = 16;
    data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len);
    return len;
  }

  private static short deriveKey(byte[] scratchPad) {
    // For KeyBlob V3: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and
    // PUB_KEY.
    short index = 0;
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0);
    byte keyType = getKeyType(data[HW_PARAMETERS]);
    // Copy the relevant parameters in the scratchPad in the order
    // 1. HW_PARAMETERS
    // 2. HIDDEN_PARAMETERS
    // 3. CUSTOM_TAGS
    // 3. VERSION ( Only Version >= 1)
    // 4. PUB_KEY ( Only for Asymmetric Keys)
    short numParams = 4;
    Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals());
    Util.setShort(scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals());
    Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals());
    Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]);
    // For Asymmetric Keys include the PUB_KEY.
    if (keyType == ASYM_KEY_TYPE) {
      numParams = 5;
      Util.setShort(scratchPad, (short) 8, data[PUB_KEY]);
    }
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE);
    Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0);
    short len = 0;
    KMOperation operation = null;
    try {
      operation =
          seProvider.initSymmetricOperation(
              KMType.SIGN,
              KMType.HMAC,
              KMType.SHA2_256,
              KMType.PADDING_NONE,
              (byte) KMType.INVALID_VALUE,
              (Object) kmDataStore.getMasterKey(),
              KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY,
              (byte[]) null,
              (short) 0,
              (short) 0,
              (short) 0,
              false);

      byte arrayHeader = (byte) 0x80;
      arrayHeader |= (byte) numParams;
      ((byte[]) repository.getHeap())[authIndex] = arrayHeader;
      operation.update(repository.getHeap(), authIndex, (short) 1);

      while (index < numParams) {
        short tag = Util.getShort(scratchPad, (short) (index * 2));
        len = encoder.encode(tag, repository.getHeap(), (short) authIndex, prevReclaimIndex);
        operation.update(repository.getHeap(), authIndex, len);
        index++;
      }
      repository.reclaimMemory(MAX_AUTH_DATA_SIZE);
      // KeyDerivation:
      // 1. Do HMAC Sign, Auth data.
      // 2. HMAC Sign generates an output of 32 bytes length.
      // Consume only first 16 bytes as derived key.
      // Hmac sign.
      len = operation.sign(scratchPad, (short) 0, (short) 0, scratchPad, (short) 0);
    } finally {
      if (operation != null) {
        operation.abort();
      }
    }
    if (len < 16) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    len = 16;
    data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len);
    return len;
  }

  public static void sendResponse(APDU apdu, short err) {
    short resp = KMArray.instance((short) 1);
    err = KMError.translate(err);
    short error = KMInteger.uint_16(err);
    KMArray.cast(resp).add((short) 0, error);
    sendOutgoing(apdu, resp);
  }

  public static void generateRkpKey(byte[] scratchPad, short keyParams) {
    data[KEY_PARAMETERS] = keyParams;
    generateECKeys(scratchPad);
    // create key blob
    data[ORIGIN] = KMType.GENERATED;
    makeKeyCharacteristics(scratchPad);
    createEncryptedKeyBlob(scratchPad);
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE);
    data[KEY_BLOB] =
        encoder.encode(
            data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE);
    data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]);
    repository.reclaimMemory(MAX_KEYBLOB_SIZE);
  }

  public static short getPubKey() {
    return data[PUB_KEY];
  }

  public static short getPivateKey() {
    return data[KEY_BLOB];
  }

  /**
   * Encodes the object to the provided apdu buffer.
   *
   * @param object Object to be encoded.
   * @param apduBuf Buffer on which the encoded data is copied.
   * @param apduOff Start offset of the buffer.
   * @param maxLen Max value of the expected out length.
   * @return length of the encoded buffer.
   */
  public static short encodeToApduBuffer(
      short object, byte[] apduBuf, short apduOff, short maxLen) {
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short offset = repository.allocReclaimableMemory(maxLen);
    short len = encoder.encode(object, repository.getHeap(), offset, prevReclaimIndex, maxLen);
    Util.arrayCopyNonAtomic(repository.getHeap(), offset, apduBuf, apduOff, len);
    // release memory
    repository.reclaimMemory(maxLen);
    return len;
  }

  public static short generateDiceCertChain(byte[] scratchPad) {
    if (kmDataStore.isProvisionLocked()) {
      KMException.throwIt(KMError.STATUS_FAILED);
    }
    KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair();
    short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0);
    short coseKey =
        KMCose.constructCoseKey(
            rkp.rkpTmpVariables,
            KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2),
            KMType.INVALID_VALUE,
            KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
            KMInteger.uint_8(KMCose.COSE_ECCURVE_256),
            scratchPad,
            (short) 0,
            temp,
            KMType.INVALID_VALUE);
    temp =
        KMKeymasterApplet.encodeToApduBuffer(
            coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
    // Construct payload.
    short payload =
        KMCose.constructCoseCertPayload(
            KMCosePairTextStringTag.instance(
                KMInteger.uint_8(KMCose.ISSUER),
                KMTextString.instance(
                    KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)),
            KMCosePairTextStringTag.instance(
                KMInteger.uint_8(KMCose.SUBJECT),
                KMTextString.instance(
                    KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)),
            KMCosePairByteBlobTag.instance(
                KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0),
                KMByteBlob.instance(scratchPad, (short) 0, temp)),
            KMCosePairByteBlobTag.instance(
                KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0),
                KMByteBlob.instance(
                    KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length)));
    // temp temporarily holds the length of encoded cert payload.
    temp =
        KMKeymasterApplet.encodeToApduBuffer(
            payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
    payload = KMByteBlob.instance(scratchPad, (short) 0, temp);

    // protected header
    short protectedHeader =
        KMCose.constructHeaders(
            rkp.rkpTmpVariables,
            KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE,
            KMType.INVALID_VALUE);
    // temp temporarily holds the length of encoded headers.
    temp =
        KMKeymasterApplet.encodeToApduBuffer(
            protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
    protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp);

    // unprotected headers.
    short arr = KMArray.instance((short) 0);
    short unprotectedHeader = KMCoseHeaders.instance(arr);

    // construct cose sign structure.
    short coseSignStructure =
        KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload);
    // temp temporarily holds the length of encoded sign structure.
    // Encode cose Sign_Structure.
    temp =
        KMKeymasterApplet.encodeToApduBuffer(
            coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
    // do sign
    short len =
        seProvider.signWithDeviceUniqueKey(
            deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp);
    len =
        KMAsn1Parser.instance()
            .decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp);
    coseSignStructure = KMByteBlob.instance(scratchPad, temp, len);

    // construct cose_sign1
    short coseSign1 =
        KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure);

    // [Cose_Key, Cose_Sign1]
    short dcc = KMArray.instance((short) 2);
    KMArray.cast(dcc).add((short) 0, coseKey);
    KMArray.cast(dcc).add((short) 1, coseSign1);
    return dcc;
  }

  protected void initHmacNonceAndSeed() {
    short nonce = repository.alloc((short) 32);
    seProvider.newRandomNumber(
        repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE);
    kmDataStore.initHmacNonce(repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE);
  }

  private void releaseAllOperations() {
    short index = 0;
    while (index < MAX_OPERATIONS_COUNT) {
      opTable[index].reset();
      index++;
    }
  }

  private KMOperationState reserveOperation(short algorithm, short opHandle) {
    short index = 0;
    while (index < MAX_OPERATIONS_COUNT) {
      if (opTable[index].getAlgorithm() == KMType.INVALID_VALUE) {
        opTable[index].reset();
        opTable[index].setAlgorithm(algorithm);
        opTable[index].setHandle(
            KMInteger.cast(opHandle).getBuffer(),
            KMInteger.cast(opHandle).getStartOff(),
            KMInteger.cast(opHandle).length());
        return opTable[index];
      }
      index++;
    }
    return null;
  }

  private KMOperationState findOperation(short handle) {
    return findOperation(
        KMInteger.cast(handle).getBuffer(),
        KMInteger.cast(handle).getStartOff(),
        KMInteger.cast(handle).length());
  }

  private KMOperationState findOperation(byte[] opHandle, short start, short len) {
    short index = 0;
    while (index < MAX_OPERATIONS_COUNT) {
      if (opTable[index].compare(opHandle, start, len) == 0) {
        if (opTable[index].getAlgorithm() != KMType.INVALID_VALUE) {
          return opTable[index];
        }
      }
      index++;
    }
    return null;
  }

  private void releaseOperation(KMOperationState op) {
    op.reset();
  }

  /**
   * Selects this applet.
   *
   * @return Returns true if the keymaster is in correct state
   */
  @Override
  public boolean select() {
    repository.onSelect();
    return true;
  }

  /** De-selects this applet. */
  @Override
  public void deselect() {
    repository.onDeselect();
  }

  /** Uninstalls the applet after cleaning the repository. */
  @Override
  public void uninstall() {
    repository.onUninstall();
  }

  protected short mapISOErrorToKMError(short reason) {
    switch (reason) {
      case ISO7816.SW_CLA_NOT_SUPPORTED:
        return KMError.UNSUPPORTED_CLA;
      case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
        return KMError.SW_CONDITIONS_NOT_SATISFIED;
      case ISO7816.SW_COMMAND_NOT_ALLOWED:
        return KMError.CMD_NOT_ALLOWED;
      case ISO7816.SW_DATA_INVALID:
        return KMError.INVALID_DATA;
      case ISO7816.SW_INCORRECT_P1P2:
        return KMError.INVALID_P1P2;
      case ISO7816.SW_INS_NOT_SUPPORTED:
        return KMError.UNSUPPORTED_INSTRUCTION;
      case ISO7816.SW_WRONG_LENGTH:
        return KMError.SW_WRONG_LENGTH;
      case ISO7816.SW_UNKNOWN:
      default:
        return KMError.UNKNOWN_ERROR;
    }
  }

  protected short mapCryptoErrorToKMError(short reason) {
    switch (reason) {
      case CryptoException.ILLEGAL_USE:
        return KMError.CRYPTO_ILLEGAL_USE;
      case CryptoException.ILLEGAL_VALUE:
        return KMError.CRYPTO_ILLEGAL_VALUE;
      case CryptoException.INVALID_INIT:
        return KMError.CRYPTO_INVALID_INIT;
      case CryptoException.NO_SUCH_ALGORITHM:
        return KMError.CRYPTO_NO_SUCH_ALGORITHM;
      case CryptoException.UNINITIALIZED_KEY:
        return KMError.CRYPTO_UNINITIALIZED_KEY;
      default:
        return KMError.UNKNOWN_ERROR;
    }
  }

  public void updateApduStatusFlags(short apduIns) {
    switch (apduIns) {
      case INS_EXPORT_KEY_CMD:
      case INS_DELETE_ALL_KEYS_CMD:
      case INS_DESTROY_ATT_IDS_CMD:
      case INS_VERIFY_AUTHORIZATION_CMD:
      case INS_GET_HMAC_SHARING_PARAM_CMD:
      case INS_GET_HW_INFO_CMD:
      case INS_EARLY_BOOT_ENDED_CMD:
      case INS_GET_ROT_CHALLENGE_CMD:
      case INS_GET_ROT_DATA_CMD:
      case INS_GET_RKP_HARDWARE_INFO:
      case INS_FINISH_SEND_DATA_CMD:
      case INS_GET_UDS_CERTS_CMD:
      case INS_GET_DICE_CERT_CHAIN_CMD:
        apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0;
        break;
      default:
        // By default the instruction is set to case 4 command instruction.
        break;
    }
  }

  /**
   * Processes an incoming APDU and handles it using command objects.
   *
   * @param apdu the incoming APDU
   */
  @Override
  public void process(APDU apdu) {
    try {
      resetTransientBuffers();
      repository.onProcess();
      // If this is select applet apdu which is selecting this applet then return
      if (apdu.isISOInterindustryCLA()) {
        if (selectingApplet()) {
          return;
        }
      }
      byte[] apduBuffer = apdu.getBuffer();
      byte apduIns = apduBuffer[ISO7816.OFFSET_INS];
      if (!isKeyMintReady(apduIns)) {
        ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
      }
      switch (apduIns) {
        case INS_INIT_STRONGBOX_CMD:
          processInitStrongBoxCmd(apdu);
          sendResponse(apdu, KMError.OK);
          return;
        case INS_GENERATE_KEY_CMD:
          processGenerateKey(apdu);
          break;
        case INS_IMPORT_KEY_CMD:
          processImportKeyCmd(apdu);
          break;
        case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD:
          processBeginImportWrappedKeyCmd(apdu);
          break;
        case INS_FINISH_IMPORT_WRAPPED_KEY_CMD:
          processFinishImportWrappedKeyCmd(apdu);
          break;
        case INS_EXPORT_KEY_CMD:
          processExportKeyCmd(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_DEVICE_LOCKED_CMD:
          processDeviceLockedCmd(apdu);
          break;
        case INS_EARLY_BOOT_ENDED_CMD:
          processEarlyBootEndedCmd(apdu);
          break;
        case INS_UPDATE_AAD_OPERATION_CMD:
          processUpdateAadOperationCmd(apdu);
          break;
        case INS_GENERATE_RKP_KEY_CMD:
        case INS_BEGIN_SEND_DATA_CMD:
        case INS_UPDATE_KEY_CMD:
        case INS_FINISH_SEND_DATA_CMD:
        case INS_GET_RKP_HARDWARE_INFO:
        case INS_GET_UDS_CERTS_CMD:
        case INS_GET_DICE_CERT_CHAIN_CMD:
          rkp.process(apduIns, apdu);
          break;
        // KeyMint 2.0
        case INS_GET_ROT_CHALLENGE_CMD:
          processGetRootOfTrustChallenge(apdu);
          break;
        case INS_GET_ROT_DATA_CMD:
          sendResponse(apdu, KMError.UNIMPLEMENTED);
          break;
        case INS_SEND_ROT_DATA_CMD:
          processSendRootOfTrust(apdu);
          break;
        default:
          ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
      }
    } catch (KMException exception) {
      freeOperations();
      resetWrappingKey();
      sendResponse(apdu, KMException.reason());
    } catch (ISOException exp) {
      freeOperations();
      resetWrappingKey();
      sendResponse(apdu, mapISOErrorToKMError(exp.getReason()));
    } catch (CryptoException e) {
      freeOperations();
      resetWrappingKey();
      sendResponse(apdu, mapCryptoErrorToKMError(e.getReason()));
    } catch (Exception e) {
      freeOperations();
      resetWrappingKey();
      sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR);
    } finally {
      repository.clean();
    }
  }

  private void processGetRootOfTrustChallenge(APDU apdu) {
    byte[] scratchpad = apdu.getBuffer();
    // Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of
    // trust data.
    seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16);
    kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16);
    short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16);
    short arr = KMArray.instance((short) 2);
    KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(arr).add((short) 1, challenge);
    sendOutgoing(apdu, arr);
  }

  private short sendRootOfTrustCmd(APDU apdu) {
    short arrInst = KMArray.instance((short) 4);
    short headers = KMCoseHeaders.exp();
    KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp());
    KMArray.cast(arrInst).add((short) 1, headers);
    KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp());
    KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp());
    short semanticTag = KMSemanticTag.exp(arrInst);
    short arr = KMArray.exp(semanticTag);
    return receiveIncoming(apdu, arr);
  }

  private void processSendRootOfTrust(APDU apdu) {
    byte[] scratchPad = apdu.getBuffer();
    short cmd = KMType.INVALID_VALUE;
    // As per VTS if the input data is empty or not well-formed
    // CoseMac return VERIFICATION_FAILED error.
    try {
      cmd = sendRootOfTrustCmd(apdu);
    } catch (Exception e) {
      KMException.throwIt(KMError.VERIFICATION_FAILED);
    }

    short semanticTag = KMArray.cast(cmd).get((short) 0);
    short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr();
    // Exp for KMCoseHeaders
    short coseHeadersExp = KMCoseHeaders.exp();
    // validate protected Headers
    short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET);
    ptr =
        decoder.decode(
            coseHeadersExp,
            KMByteBlob.cast(ptr).getBuffer(),
            KMByteBlob.cast(ptr).getStartOff(),
            KMByteBlob.cast(ptr).length());

    if (!KMCoseHeaders.cast(ptr)
        .isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) {
      KMException.throwIt(KMError.VERIFICATION_FAILED);
    }

    // Validate the Mac
    short len = kmDataStore.getChallenge(scratchPad, (short) 0);
    short extAad = KMByteBlob.instance(scratchPad, (short) 0, len);
    // Compute CoseMac Structure and compare the macs.
    short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET);
    short macStructure =
        KMCose.constructCoseMacStructure(
            KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET),
            extAad,
            rotPayload);
    short encodedLen =
        KMKeymasterApplet.encodeToApduBuffer(
            macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);

    if (!seProvider.hmacVerify(
        kmDataStore.getComputedHmacKey(),
        scratchPad,
        (short) 0,
        encodedLen,
        KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(),
        KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(),
        KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) {
      KMException.throwIt(KMError.VERIFICATION_FAILED);
    }
    // Store the data only once after reboot.
    // Allow set boot params only when the host device reboots and the applet is in
    // active state. If host does not support boot signal event, then allow this
    // instruction any time.
    kmDataStore.getDeviceBootStatus(scratchPad, (short) 0);
    if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) {
      // store the data.
      storeRootOfTrust(rotPayload, scratchPad);
      kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS);
    }
    // Invalidate the challenge
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0);
    kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16);
    sendResponse(apdu, KMError.OK);
  }

  private void storeRootOfTrust(short rotPayload, byte[] scratchPad) {
    short byteBlobExp = KMByteBlob.exp();
    short intExp = KMInteger.exp();
    short boolExp = KMSimpleValue.exp();
    short arr = KMArray.instance((short) 5);
    KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key.
    KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked.
    KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State.
    KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash.
    KMArray.cast(arr).add((short) 4, intExp); // Boot patch level
    short semanticExp = KMSemanticTag.exp(arr);

    short semanticPtr =
        decoder.decode(
            semanticExp,
            KMByteBlob.cast(rotPayload).getBuffer(),
            KMByteBlob.cast(rotPayload).getStartOff(),
            KMByteBlob.cast(rotPayload).length());
    short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr();
    // Store verified boot key
    short ptr = KMArray.cast(rotArr).get((short) 0);
    kmDataStore.setBootKey(
        KMByteBlob.cast(ptr).getBuffer(),
        KMByteBlob.cast(ptr).getStartOff(),
        KMByteBlob.cast(ptr).length());
    // Store Boot device locked.
    ptr = KMArray.cast(rotArr).get((short) 1);
    kmDataStore.setDeviceLocked(
        (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false);
    // Store verified boot state
    ptr = KMArray.cast(rotArr).get((short) 2);
    kmDataStore.setBootState(KMInteger.cast(ptr).getShort());
    // Store Verified boot hash
    ptr = KMArray.cast(rotArr).get((short) 3);
    kmDataStore.setVerifiedBootHash(
        KMByteBlob.cast(ptr).getBuffer(),
        KMByteBlob.cast(ptr).getStartOff(),
        KMByteBlob.cast(ptr).length());
    // Store boot patch level
    ptr = KMArray.cast(rotArr).get((short) 4);
    kmDataStore.setBootPatchLevel(
        KMInteger.cast(ptr).getBuffer(),
        KMInteger.cast(ptr).getStartOff(),
        KMInteger.cast(ptr).length());
  }

  // After every device boot, the Keymaster becomes ready to execute all the commands only after
  // 1. boot parameters are set,
  // 2. system properties are set and
  // 3. computed the shared secret successfully.
  protected boolean isKeyMintReady(byte apduIns) {
    if (kmDataStore.isDeviceReady()) {
      return true;
    }
    // Below commands are allowed even if the Keymaster is not ready.
    switch (apduIns) {
      case INS_GET_HW_INFO_CMD:
      case INS_GET_RKP_HARDWARE_INFO:
      case INS_ADD_RNG_ENTROPY_CMD:
      case INS_GET_HMAC_SHARING_PARAM_CMD:
      case INS_COMPUTE_SHARED_HMAC_CMD:
      case INS_EARLY_BOOT_ENDED_CMD:
      case INS_DELETE_ALL_KEYS_CMD:
      case INS_INIT_STRONGBOX_CMD:
      case INS_GET_ROT_CHALLENGE_CMD:
      case INS_SEND_ROT_DATA_CMD:
        return true;
      default:
        break;
    }
    return false;
  }

  private void generateUniqueOperationHandle(byte[] buf, short offset, short len) {
    do {
      seProvider.newRandomNumber(buf, offset, len);
    } while (null != findOperation(buf, offset, len));
  }

  private void freeOperations() {
    if (data[OP_HANDLE] != KMType.INVALID_VALUE) {
      KMOperationState op = findOperation(data[OP_HANDLE]);
      if (op != null) {
        releaseOperation(op);
      }
    }
  }

  private void processEarlyBootEndedCmd(APDU apdu) {
    kmDataStore.setEarlyBootEndedStatus(true);
    sendResponse(apdu, KMError.OK);
  }

  private short deviceLockedCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 2);
    short ptr = KMVerificationToken.exp();
    // passwordOnly
    KMArray.cast(cmd).add((short) 0, KMInteger.exp());
    // verification token
    KMArray.cast(cmd).add((short) 1, ptr);
    return receiveIncoming(apdu, cmd);
  }

  private void processDeviceLockedCmd(APDU apdu) {
    short cmd = deviceLockedCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    short passwordOnly = KMArray.cast(cmd).get((short) 0);
    short verToken = KMArray.cast(cmd).get((short) 1);
    passwordOnly = KMInteger.cast(passwordOnly).getByte();
    validateVerificationToken(verToken, scratchPad);
    short verTime = KMVerificationToken.cast(verToken).getTimestamp();
    short lastDeviceLockedTime;
    try {
      lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp();
    } catch (KMException e) {
      lastDeviceLockedTime = KMInteger.uint_8((byte) 0);
    }
    if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) {
      Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0);
      KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64);
      kmDataStore.setDeviceLock(true);
      kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01);
      kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64);
    }
    sendResponse(apdu, KMError.OK);
  }

  private void resetWrappingKey() {
    if (!isValidWrappingKey()) {
      return;
    }
    Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0);
    wrappingKey[0] = -1;
  }

  private boolean isValidWrappingKey() {
    return wrappingKey[0] != -1;
  }

  private short getWrappingKey() {
    return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE);
  }

  private void setWrappingKey(short key) {
    if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    wrappingKey[0] = 0;
    Util.arrayCopyNonAtomic(
        KMByteBlob.cast(key).getBuffer(),
        KMByteBlob.cast(key).getStartOff(),
        wrappingKey,
        (short) 1,
        WRAPPING_KEY_SIZE);
  }

  protected void resetTransientBuffers() {
    short index = 0;
    while (index < data.length) {
      data[index] = KMType.INVALID_VALUE;
      index++;
    }
    index = 0;
    while (index < tmpVariables.length) {
      tmpVariables[index] = KMType.INVALID_VALUE;
      index++;
    }
  }

  public void sendOutgoing(
      APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) {
    // This is the special case where the output is encoded manually without using
    // the encoder algorithm. Encoder creates a duplicate copy for each KMType Object.
    // The output of the generateKey, importKey and importWrappedKey commands are huge so
    // by manually encoding we can avoid duplicate copies.
    // The output data is directly written to the end of heap in the below order
    // output = [
    //     errorCode  : uint // ErrorCode
    //     keyBlob    : bstr // KeyBlob.
    //     keyChars
    //     certifcate
    // ]
    // certificate = [
    //     x509_cert : bstr // X509 certificate
    // ]
    // keyChars = {  // Map
    // }
    byte[] buffer = repository.getHeap();

    if (cert == null) {
      // This happens for Symmetric keys.
      short bufferStart = repository.allocReclaimableMemory((short) 1);
      buffer[bufferStart] = (byte) 0x80; // Array of 0 length.
    } else {
      // Encode the certificate into cbor data at the end of the heap
      // certData = [
      //     x509_cert : bstr // X509 certificate
      // ]
      short bufferStart =
          encoder.encodeCert(
              repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength());
      // reclaim the unused memory in the certificate.
      repository.reclaimMemory((short) (bufferStart - certStart));
    }

    // Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE]
    encodeKeyCharacteristics(keyChars);
    // and encode it to the end of the buffer before KEY_CHARACTERISTICS
    encodeKeyBlob(keyblob);
    // Write Array header and ErrorCode before data[KEY_BLOB]
    short bufferStartOffset = repository.allocReclaimableMemory((short) 2);
    Util.setShort(buffer, bufferStartOffset, (short) 0x8400);

    short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset);
    /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must
     * be invoked prior to calling setOutgoing(). Otherwise, erroneous
     * behavior may result
     * */
    if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1
        && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0
        && APDU.getProtocol() == APDU.PROTOCOL_T0) {
      apdu.setIncomingAndReceive();
    }
    // Send data
    apdu.setOutgoing();
    apdu.setOutgoingLength(bufferLength);
    apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength);
  }

  private void processGetHwInfoCmd(APDU apdu) {
    // No arguments expected
    // Make the response
    short respPtr = KMArray.instance((short) 6);
    KMArray resp = KMArray.cast(respPtr);
    resp.add((short) 0, KMInteger.uint_16(KMError.OK));
    resp.add((short) 1, KMInteger.uint_16(getPackageVersion()));
    resp.add((short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX));
    resp.add(
        (short) 3,
        KMByteBlob.instance(
            JavacardKeymintDevice, (short) 0, (short) JavacardKeymintDevice.length));
    resp.add((short) 4, KMByteBlob.instance(Google, (short) 0, (short) Google.length));
    resp.add((short) 5, KMInteger.uint_8((byte) 1));
    // send buffer to host
    sendOutgoing(apdu, respPtr);
  }

  private short addRngEntropyCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 1);
    // Rng entropy
    KMArray.cast(cmd).add((short) 0, KMByteBlob.exp());
    return receiveIncoming(apdu, cmd);
  }

  private void processAddRngEntropyCmd(APDU apdu) {
    // Receive the incoming request fully from the host.
    short cmd = addRngEntropyCmd(apdu);
    // Process
    KMByteBlob blob = KMByteBlob.cast(KMArray.cast(cmd).get((short) 0));
    // Maximum 2KiB of seed is allowed.
    if (blob.length() > MAX_SEED_SIZE) {
      KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
    }
    seProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length());
    sendResponse(apdu, KMError.OK);
  }

  private short getKeyCharacteristicsCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 3);
    KMArray.cast(cmd).add((short) 0, KMByteBlob.exp());
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
    KMArray.cast(cmd).add((short) 2, KMByteBlob.exp());
    return receiveIncoming(apdu, cmd);
  }

  private void processGetKeyCharacteristicsCmd(APDU apdu) {
    // Receive the incoming request fully from the host.
    short cmd = getKeyCharacteristicsCmd(apdu);
    // Re-purpose the apdu buffer as scratch pad.
    byte[] scratchPad = apdu.getBuffer();
    data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0);
    data[APP_ID] = KMArray.cast(cmd).get((short) 1);
    data[APP_DATA] = KMArray.cast(cmd).get((short) 2);
    if (KMByteBlob.cast(data[APP_ID]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE
        || KMByteBlob.cast(data[APP_DATA]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE) {
      ISOException.throwIt(ISO7816.SW_DATA_INVALID);
    }
    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;
    }
    // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired
    // function itself.
    if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) {
      KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
    }
    // make response.
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, data[KEY_CHARACTERISTICS]);
    sendOutgoing(apdu, resp);
  }

  private void processGetHmacSharingParamCmd(APDU apdu) {
    // No Arguments
    // Create HMAC Sharing Parameters
    short params = KMHmacSharingParameters.instance();
    short nonce = kmDataStore.getHmacNonce();
    short seed = KMByteBlob.instance((short) 0);
    KMHmacSharingParameters.cast(params).setNonce(nonce);
    KMHmacSharingParameters.cast(params).setSeed(seed);
    // prepare the response
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, params);
    sendOutgoing(apdu, resp);
  }

  private void processDeleteAllKeysCmd(APDU apdu) {
    // No arguments
    // This function is triggered when a factory reset event occurs.
    // Regenerate the master key to render all keys unusable.
    kmDataStore.regenerateMasterKey();
    // Send ok
    sendResponse(apdu, KMError.OK);
  }

  private short createKeyBlobExp(short version) {
    short keyBlob = KMType.INVALID_VALUE;
    short byteBlobExp = KMByteBlob.exp();
    short keyChar = KMKeyCharacteristics.exp();
    short keyParam = KMKeyParameters.exp();
    switch (version) {
      case (short) 0:
        // Old KeyBlob has a maximum of 5 elements.
        keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V0);
        KMArray.cast(keyBlob).add((short) 0, byteBlobExp); // Secret
        KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Nonce
        KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // AuthTag
        KMArray.cast(keyBlob).add((short) 3, keyChar); // KeyChars
        KMArray.cast(keyBlob).add((short) 4, byteBlobExp); // PubKey
        break;
      case (short) 1:
        keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V1);
        KMArray.cast(keyBlob).add((short) 0, KMInteger.exp()); // Version
        KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Secret
        KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // Nonce
        KMArray.cast(keyBlob).add((short) 3, byteBlobExp); // AuthTag
        KMArray.cast(keyBlob).add((short) 4, keyChar); // KeyChars
        KMArray.cast(keyBlob).add((short) 5, byteBlobExp); // PubKey
        break;
      case (short) 2:
      case (short) 3:
        keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_VERSION_OFFSET, KMInteger.exp());
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_SECRET, byteBlobExp);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, byteBlobExp);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_NONCE, byteBlobExp);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PARAMS, keyChar);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_CUSTOM_TAGS, keyParam);
        KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, byteBlobExp);
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    return keyBlob;
  }

  private void processDeleteKeyCmd(APDU apdu) {
    // Send ok
    sendResponse(apdu, KMError.OK);
  }

  private short computeSharedHmacCmd(APDU apdu) {
    short params = KMHmacSharingParameters.exp();
    short paramsVec = KMArray.exp(params);
    short cmd = KMArray.instance((short) 1);
    KMArray.cast(cmd).add((short) 0, paramsVec);
    return receiveIncoming(apdu, cmd);
  }

  private void processComputeSharedHmacCmd(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = computeSharedHmacCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    data[HMAC_SHARING_PARAMS] = KMArray.cast(cmd).get((short) 0);
    // Concatenate HMAC Params
    // tmpVariables[0]
    short paramsLen = KMArray.cast(data[HMAC_SHARING_PARAMS]).length(); // total number of params
    // tmpVariables[1]
    short concateBuffer = repository.alloc((short) (paramsLen * HMAC_SHARED_PARAM_MAX_SIZE));
    // tmpVariables[2]
    short paramIndex = 0; // index for params
    // tmpVariables[3]
    short bufferIndex = 0; // index for concatenation buffer
    // To check if nonce created by Strongbox is found. This value becomes 1 if both
    // seed and nonce created here are found in hmac sharing parameters received.
    // tmpVariables[7] = 0;
    short found = 0;
    // tmpVariables[9]
    short nonce = kmDataStore.getHmacNonce();

    while (paramIndex < paramsLen) {
      // read HmacSharingParam
      // tmpVariables[4]
      short param = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(paramIndex);
      // get seed - 32 bytes max
      // tmpVariables[5]
      short seed = KMHmacSharingParameters.cast(param).getSeed();
      // tmpVariables[6]
      short seedLength = KMByteBlob.cast(seed).length();
      // if seed is present
      if (seedLength != 0) {
        // then copy that to concatenation buffer
        Util.arrayCopyNonAtomic(
            KMByteBlob.cast(seed).getBuffer(),
            KMByteBlob.cast(seed).getStartOff(),
            repository.getHeap(),
            (short) (concateBuffer + bufferIndex), // concat index
            seedLength);
        bufferIndex += seedLength; // increment the concat index
      } else if (found == 0) {
        found = 1; // Applet does not have any seed. Potentially
      }
      // if nonce is present get nonce - 32 bytes
      // tmpVariables[5]
      short paramNonce = KMHmacSharingParameters.cast(param).getNonce();
      short nonceLen = KMByteBlob.cast(paramNonce).length();
      // if nonce is less then 32 - it is an error
      if (nonceLen < 32) {
        KMException.throwIt(KMError.INVALID_ARGUMENT);
      }
      // copy nonce to concatenation buffer
      Util.arrayCopyNonAtomic(
          KMByteBlob.cast(paramNonce).getBuffer(),
          KMByteBlob.cast(paramNonce).getStartOff(),
          repository.getHeap(),
          (short) (concateBuffer + bufferIndex), // index
          nonceLen);

      // Check if the nonce generated here is present in the hmacSharingParameters array.
      // Otherwise throw INVALID_ARGUMENT error.
      if (found == 1) {
        if (0
            == Util.arrayCompare(
                repository.getHeap(),
                (short) (concateBuffer + bufferIndex),
                KMByteBlob.cast(nonce).getBuffer(),
                KMByteBlob.cast(nonce).getStartOff(),
                nonceLen)) {
          found = 2; // hmac nonce for this keymaster found.
        } else {
          found = 0;
        }
      }
      bufferIndex += nonceLen; // increment by nonce length
      paramIndex++; // go to next hmac param in the vector
    }
    if (found != 2) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }
    // generate the key and store it in scratch pad - 32 bytes
    // tmpVariables[6]
    short keyLen =
        seProvider.cmacKDF(
            kmDataStore.getPresharedKey(),
            ckdfLabel,
            (short) 0,
            (short) ckdfLabel.length,
            repository.getHeap(),
            concateBuffer,
            bufferIndex,
            scratchPad,
            (short) 0);

    // persist the computed hmac key.
    kmDataStore.createComputedHmacKey(scratchPad, (short) 0, keyLen);
    // Generate sharingKey verification signature and store that in scratch pad.
    // tmpVariables[5]
    short signLen =
        seProvider.hmacSign(
            scratchPad,
            (short) 0,
            keyLen,
            sharingCheck,
            (short) 0,
            (short) sharingCheck.length,
            scratchPad,
            keyLen);
    kmDataStore.setDeviceBootStatus(KMKeymintDataStore.NEGOTIATED_SHARED_SECRET_SUCCESS);
    // verification signature blob - 32 bytes
    // tmpVariables[1]
    short signature = KMByteBlob.instance(scratchPad, keyLen, signLen);
    // prepare the response
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, signature);
    sendOutgoing(apdu, resp);
  }

  private short upgradeKeyCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 2);
    short keyParams = KMKeyParameters.exp();
    KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Key Blob
    KMArray.cast(cmd).add((short) 1, keyParams); // Key Params
    return receiveIncoming(apdu, cmd);
  }

  private boolean isKeyUpgradeRequired(
      short keyBlob, short appId, short appData, byte[] scratchPad) {
    // Check if the KeyBlob is compatible. If there is any change in the KeyBlob, the version
    // Parameter in the KeyBlob should be updated to the next version.
    short version = readKeyBlobVersion(keyBlob);
    parseEncryptedKeyBlob(keyBlob, appId, appData, scratchPad, version);
    if (version < KEYBLOB_CURRENT_VERSION) {
      return true;
    }
    short bootPatchLevel = kmDataStore.getBootPatchLevel();
    // Fill the key-value properties in the scratchpad
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0);
    Util.setShort(scratchPad, (short) 0, KMType.OS_VERSION);
    Util.setShort(scratchPad, (short) 2, kmDataStore.getOsVersion());
    Util.setShort(scratchPad, (short) 4, KMType.OS_PATCH_LEVEL);
    Util.setShort(scratchPad, (short) 6, kmDataStore.getOsPatch());
    Util.setShort(scratchPad, (short) 8, KMType.VENDOR_PATCH_LEVEL);
    Util.setShort(scratchPad, (short) 10, kmDataStore.getVendorPatchLevel());
    Util.setShort(scratchPad, (short) 12, KMType.BOOT_PATCH_LEVEL);
    Util.setShort(scratchPad, (short) 14, bootPatchLevel);
    short index = 0;
    short tag;
    short systemParam;
    boolean isKeyUpgradeRequired = false;
    while (index < 16) {
      tag = Util.getShort(scratchPad, index);
      systemParam = Util.getShort(scratchPad, (short) (index + 2));
      // validate the tag and check if key needs upgrade.
      short tagValue = KMKeyParameters.findTag(KMType.UINT_TAG, tag, data[HW_PARAMETERS]);
      tagValue = KMIntegerTag.cast(tagValue).getValue();
      short zero = KMInteger.uint_8((byte) 0);
      if (tagValue != 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 ((tag == KMType.OS_VERSION
            && KMInteger.compare(tagValue, systemParam) == 1
            && KMInteger.compare(systemParam, zero) == 0)) {
          // Key needs upgrade.
          isKeyUpgradeRequired = true;
        } else if ((KMInteger.compare(tagValue, systemParam) == -1)) {
          // Each os version or patch level associated with the key must be less than it's
          // corresponding value stored in Javacard, then only upgrade is allowed otherwise it
          // is invalid argument.
          isKeyUpgradeRequired = true;
        } else if (KMInteger.compare(tagValue, systemParam) == 1) {
          KMException.throwIt(KMError.INVALID_ARGUMENT);
        }
      } else {
        KMException.throwIt(KMError.UNKNOWN_ERROR);
      }
      index += 4;
    }
    return isKeyUpgradeRequired;
  }

  private void processUpgradeKeyCmd(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = upgradeKeyCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();

    short keyBlob = KMArray.cast(cmd).get((short) 0);
    data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1);
    short appId = getApplicationId(data[KEY_PARAMETERS]);
    short appData = getApplicationData(data[KEY_PARAMETERS]);

    data[KEY_BLOB] = KMType.INVALID_VALUE;
    // Check if the KeyBlob requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired
    // function itself, but if there is a difference in the KeyBlob version isKeyUpgradeRequired()
    // does not parse the KeyBlob.
    boolean isKeyUpgradeRequired = isKeyUpgradeRequired(keyBlob, appId, appData, scratchPad);
    if (isKeyUpgradeRequired) {
      // copy origin
      data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]);
      byte keyType = getKeyType(data[HW_PARAMETERS]);
      switch (keyType) {
        case ASYM_KEY_TYPE:
          data[KEY_BLOB] = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3);
          KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
          break;
        case SYM_KEY_TYPE:
          data[KEY_BLOB] = KMArray.instance(SYM_KEY_BLOB_SIZE_V2_V3);
          break;
        default:
          KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
      }
      // Update the system properties to the latest values and also re-create the KeyBlob's
      // KeyCharacteristics to make sure all the values are up-to-date with the latest applet
      // changes.
      upgradeKeyBlobKeyCharacteristics(data[HW_PARAMETERS], scratchPad);
      // create new key blob with current os version etc.
      createEncryptedKeyBlob(scratchPad);
      short prevReclaimIndex = repository.getHeapReclaimIndex();
      short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE);
      data[KEY_BLOB] =
          encoder.encode(
              data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE);
      data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]);
      repository.reclaimMemory(MAX_KEYBLOB_SIZE);
    } else {
      data[KEY_BLOB] = KMByteBlob.instance((short) 0);
    }
    // prepare the response
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, data[KEY_BLOB]);
    sendOutgoing(apdu, resp);
  }

  private void processExportKeyCmd(APDU apdu) {
    sendResponse(apdu, KMError.UNIMPLEMENTED);
  }

  private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) {
    // Read App Id and App Data if any from un wrapping key params
    data[APP_ID] = getApplicationId(wrapParams);
    data[APP_DATA] = getApplicationData(wrapParams);
    data[KEY_PARAMETERS] = wrapParams;
    data[KEY_BLOB] = keyBlob;
    // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired
    // function itself.
    if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) {
      KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
    }
    validateWrappingKeyBlob();
  }

  private void validateWrappingKeyBlob() {
    // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest
    // SHA2_256.
    KMTag.assertPresence(
        data[SB_PARAMETERS],
        KMType.ENUM_TAG,
        KMType.ALGORITHM,
        KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM);
    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));
    }

    // Check that the digest and padding mode specified in unwrapping  parameters are SHA2_256
    // and RSA_OAEP respectively.
    if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.INCOMPATIBLE_DIGEST);
    }
    if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
    }
  }

  private short decryptTransportKey(
      short privExp, short modulus, short transportKey, byte[] scratchPad) {
    short length =
        seProvider.rsaDecipherOAEP256(
            KMByteBlob.cast(privExp).getBuffer(),
            KMByteBlob.cast(privExp).getStartOff(),
            KMByteBlob.cast(privExp).length(),
            KMByteBlob.cast(modulus).getBuffer(),
            KMByteBlob.cast(modulus).getStartOff(),
            KMByteBlob.cast(modulus).length(),
            KMByteBlob.cast(transportKey).getBuffer(),
            KMByteBlob.cast(transportKey).getStartOff(),
            KMByteBlob.cast(transportKey).length(),
            scratchPad,
            (short) 0);
    return KMByteBlob.instance(scratchPad, (short) 0, length);
  }

  private void unmask(short data, short maskingKey) {
    short dataLength = KMByteBlob.cast(data).length();
    short maskLength = KMByteBlob.cast(maskingKey).length();
    // Length of masking key and transport key must be same.
    if (maskLength != dataLength) {
      KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
    }
    short index = 0; // index
    // Xor every byte of masking and key and store the result in data[SECRET]
    while (index < maskLength) {
      short var1 = (short) (((short) KMByteBlob.cast(maskingKey).get(index)) & 0x00FF);
      short var2 = (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF);
      KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2));
      index++;
    }
  }

  private short beginImportWrappedKeyCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 4);
    short params = KMKeyParameters.expAny();
    KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Encrypted Transport Key
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob
    KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Masking Key
    params = KMKeyParameters.exp();
    KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params
    return receiveIncoming(apdu, cmd);
  }

  private void processBeginImportWrappedKeyCmd(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = beginImportWrappedKeyCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    // Step -1 parse the wrapping key blob
    // read wrapping key blob
    short keyBlob = KMArray.cast(cmd).get((short) 1);
    // read un wrapping key params
    short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3);
    processWrappingKeyBlob(keyBlob, wrappingKeyParameters, scratchPad);
    // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key
    short transportKey =
        decryptTransportKey(
            data[SECRET], data[PUB_KEY], KMArray.cast(cmd).get((short) 0), scratchPad);
    // Step 3 - XOR the decrypted AES-GCM key with with masking key
    unmask(transportKey, KMArray.cast(cmd).get((short) 2));
    if (isValidWrappingKey()) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    setWrappingKey(transportKey);
    sendResponse(apdu, KMError.OK);
  }

  private short aesGCMDecrypt(
      short aesSecret, short input, short nonce, short authData, short authTag) {
    short outPtr = KMByteBlob.instance(KMByteBlob.cast(input).length());
    if (!seProvider.aesGCMDecrypt(
        KMByteBlob.cast(aesSecret).getBuffer(),
        KMByteBlob.cast(aesSecret).getStartOff(),
        KMByteBlob.cast(aesSecret).length(),
        KMByteBlob.cast(input).getBuffer(),
        KMByteBlob.cast(input).getStartOff(),
        KMByteBlob.cast(input).length(),
        KMByteBlob.cast(outPtr).getBuffer(),
        KMByteBlob.cast(outPtr).getStartOff(),
        KMByteBlob.cast(nonce).getBuffer(),
        KMByteBlob.cast(nonce).getStartOff(),
        KMByteBlob.cast(nonce).length(),
        KMByteBlob.cast(authData).getBuffer(),
        KMByteBlob.cast(authData).getStartOff(),
        KMByteBlob.cast(authData).length(),
        KMByteBlob.cast(authTag).getBuffer(),
        KMByteBlob.cast(authTag).getStartOff(),
        KMByteBlob.cast(authTag).length())) {
      KMException.throwIt(KMError.VERIFICATION_FAILED);
    }
    return outPtr;
  }

  private short finishImportWrappedKeyCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 8);
    short params = KMKeyParameters.expAny();
    KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key
    KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format
    KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob
    KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag
    KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce
    KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA
    KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid
    KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid
    return receiveIncoming(apdu, cmd);
  }

  // TODO remove cmd later on
  private void processFinishImportWrappedKeyCmd(APDU apdu) {
    short cmd = finishImportWrappedKeyCmd(apdu);
    short keyParameters = KMArray.cast(cmd).get((short) 0);
    short keyFmt = KMArray.cast(cmd).get((short) 1);
    keyFmt = KMEnum.cast(keyFmt).getVal();
    validateImportKey(keyParameters, keyFmt);
    byte[] scratchPad = apdu.getBuffer();
    // Step 4 - AES-GCM decrypt the wrapped key
    data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2);
    data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3);
    data[NONCE] = KMArray.cast(cmd).get((short) 4);
    data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5);

    if (!isValidWrappingKey()) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    data[IMPORTED_KEY_BLOB] =
        aesGCMDecrypt(
            getWrappingKey(), data[INPUT_DATA], data[NONCE], data[AUTH_DATA], data[AUTH_TAG]);
    resetWrappingKey();
    // Step 5 - Import decrypted key
    data[ORIGIN] = KMType.SECURELY_IMPORTED;
    data[KEY_PARAMETERS] = keyParameters;
    // create key blob array
    importKey(apdu, keyFmt, scratchPad);
  }

  private KMAttestationCert makeCommonCert(byte[] scratchPad) {
    short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]);
    boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA;
    KMAttestationCert cert =
        KMAttestationCertImpl.instance(rsaCert, halVersion(), attestVersion(), seProvider);

    short subject =
        KMKeyParameters.findTag(
            KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]);

    // If no subject name is specified then use the default subject name.
    if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) {
      subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length);
    } else {
      subject = KMByteTag.cast(subject).getValue();
    }
    cert.subjectName(subject);
    // Validity period must be specified
    short notBefore = getCertificateValidityDate(KMType.CERTIFICATE_NOT_BEFORE, scratchPad);
    short notAfter = getCertificateValidityDate(KMType.CERTIFICATE_NOT_AFTER, scratchPad);
    // VTS sends notBefore == Epoch.
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0);
    short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8);
    short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length);
    if (KMInteger.compare(notBefore, epoch) == 0) {
      cert.notBefore(
          KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad);
    } else {
      cert.notBefore(notBefore, false, scratchPad);
    }
    // VTS sends notAfter == Dec 31st 9999
    if (KMInteger.compare(notAfter, end) == 0) {
      cert.notAfter(
          KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad);
    } else {
      cert.notAfter(notAfter, false, scratchPad);
    }
    // Serial number
    short serialNum =
        KMKeyParameters.findTag(
            KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]);
    if (serialNum != KMType.INVALID_VALUE) {
      serialNum = KMBignumTag.cast(serialNum).getValue();
    } else {
      serialNum = KMByteBlob.instance((short) 1);
      KMByteBlob.cast(serialNum).add((short) 0, (byte) 1);
    }
    cert.serialNumber(serialNum);
    return cert;
  }

  private short getCertificateValidityDate(short tag, byte[] scratchpad) {
    short error = KMError.UNKNOWN_ERROR;
    switch (tag) {
      case KMType.CERTIFICATE_NOT_AFTER:
        error = KMError.MISSING_NOT_AFTER;
        Util.arrayCopyNonAtomic(
            dec319999Ms, (short) 0, scratchpad, (short) 0, (short) dec319999Ms.length);
        break;
      case KMType.CERTIFICATE_NOT_BEFORE:
        error = KMError.MISSING_NOT_BEFORE;
        Util.arrayFillNonAtomic(scratchpad, (short) 0, (short) 8, (byte) 0);
        break;
      default:
        KMException.throwIt(KMError.INVALID_TAG);
    }
    short datePtr = KMKeyParameters.findTag(KMType.DATE_TAG, tag, data[KEY_PARAMETERS]);
    if (datePtr == KMType.INVALID_VALUE) {
      if (data[ORIGIN] == KMType.SECURELY_IMPORTED) {
        return KMInteger.instance(scratchpad, (short) 0, (short) 8);
      }
      KMException.throwIt(error);
    }
    return KMIntegerTag.cast(datePtr).getValue();
  }

  private KMAttestationCert makeAttestationCert(
      short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) {
    KMAttestationCert cert = makeCommonCert(scratchPad);

    // Read App Id and App Data.
    short appId = getApplicationId(attKeyParam);
    short appData = getApplicationData(attKeyParam);
    // Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR
    // and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function.
    short origBlob = data[KEY_BLOB];
    short pubKey = data[PUB_KEY];
    short privKey = data[SECRET];
    short hwParams = data[HW_PARAMETERS];
    short keyChars = data[KEY_CHARACTERISTICS];
    short customTags = data[CUSTOM_TAGS];
    // Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside
    // isKeyUpgradeRequired function itself.
    if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) {
      KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
    }
    // Get the private key of the attest key.
    short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET);
    // Get the KeyCharacteristics and SB param of the attest key
    short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS);
    short attestKeySbParams =
        KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced();
    // If the attest key's purpose is not "attest key" then error.
    short attKeyPurpose =
        KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams);
    if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) {
      KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
    }
    KMAsn1Parser asn1Decoder = KMAsn1Parser.instance();
    try {
      asn1Decoder.validateDerSubject(issuer);
    } catch (KMException e) {
      KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME);
    }
    if (KMByteBlob.cast(issuer).length() > KMConfigurations.MAX_SUBJECT_DER_LEN) {
      KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME);
    }
    // If issuer is not present then it is an error
    if (KMByteBlob.cast(issuer).length() <= 0) {
      KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME);
    }
    short alg = KMEnumTag.getValue(KMType.ALGORITHM, attestKeySbParams);
    if (alg == KMType.RSA) {
      short attestationKeyPublic = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY);
      cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT);
    } else if (alg == KMType.EC) {
      cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT);
    } else {
      KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
    }
    cert.attestationChallenge(attChallenge);
    cert.issuer(issuer);

    // Restore back the global variables.
    data[PUB_KEY] = pubKey;
    data[SECRET] = privKey;
    data[KEY_BLOB] = origBlob;
    data[HW_PARAMETERS] = hwParams;
    data[KEY_CHARACTERISTICS] = keyChars;
    data[CUSTOM_TAGS] = customTags;
    data[SW_PARAMETERS] =
        KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced();
    data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced();
    data[SB_PARAMETERS] =
        KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced();
    cert.publicKey(data[PUB_KEY]);

    // Save attestation application id - must be present.
    short attAppId =
        KMKeyParameters.findTag(
            KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]);
    if (attAppId == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING);
    }
    cert.extensionTag(attAppId, false);
    short moduleHashLen = kmDataStore.getModuleHash(scratchPad, (short) 0);
    if (moduleHashLen != 0) {
      short moduleHash = KMByteBlob.instance(scratchPad, (short) 0, moduleHashLen);
      short moduleHashTag = KMByteTag.instance(KMType.MODULE_HASH, moduleHash);
      cert.extensionTag(moduleHashTag, false);
    }
    // unique id byte blob - uses application id and temporal month count of
    // creation time.
    attAppId = KMByteTag.cast(attAppId).getValue();
    setUniqueId(cert, attAppId, scratchPad);
    // Add Attestation Ids if present
    addAttestationIds(cert, scratchPad);

    // Add Tags
    addTags(data[HW_PARAMETERS], true, cert);
    addTags(data[SW_PARAMETERS], false, cert);
    // Add Device Boot locked status
    cert.deviceLocked(kmDataStore.isDeviceBootLocked());
    // VB data
    cert.verifiedBootHash(getVerifiedBootHash(scratchPad));
    cert.verifiedBootKey(getBootKey(scratchPad));
    cert.verifiedBootState((byte) kmDataStore.getBootState());
    return cert;
  }

  private KMAttestationCert makeSelfSignedCert(
      short attPrivKey, short attPubKey, short mode, byte[] scratchPad) {
    KMAttestationCert cert = makeCommonCert(scratchPad);
    short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
    short subject =
        KMKeyParameters.findTag(
            KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]);
    // If no subject name is specified then use the default subject name.
    if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) {
      subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length);
    } else {
      subject = KMByteTag.cast(subject).getValue();
    }

    if (alg == KMType.RSA) {
      cert.rsaAttestKey(attPrivKey, attPubKey, (byte) mode);
    } else {
      cert.ecAttestKey(attPrivKey, (byte) mode);
    }
    cert.issuer(subject);
    cert.subjectName(subject);
    cert.publicKey(attPubKey);
    return cert;
  }

  protected short getBootKey(byte[] scratchPad) {
    Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE, (byte) 0);
    short len = kmDataStore.getBootKey(scratchPad, (short) 0);
    if (len != VERIFIED_BOOT_KEY_SIZE) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE);
  }

  protected short getVerifiedBootHash(byte[] scratchPad) {
    Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE, (byte) 0);
    short len = kmDataStore.getVerifiedBootHash(scratchPad, (short) 0);
    if (len != VERIFIED_BOOT_HASH_SIZE) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE);
  }

  // --------------------------------
  // Only add the Attestation ids which are requested in the attestation parameters.
  // If the requested attestation ids are not provisioned or deleted then
  // throw CANNOT_ATTEST_IDS error. If there is mismatch in the attestation
  // id values of both the requested parameters and the provisioned parameters
  // then throw INVALID_TAG error.
  private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) {
    byte index = 0;
    short attIdTag;
    short attIdTagValue;
    short storedAttIdLen;
    while (index < (short) attTags.length) {
      attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]);
      if (attIdTag != KMType.INVALID_VALUE) {
        attIdTagValue = KMByteTag.cast(attIdTag).getValue();
        storedAttIdLen = kmDataStore.getAttestationId(attTags[index], scratchPad, (short) 0);
        // Return CANNOT_ATTEST_IDS if Attestation IDs are not provisioned or
        // Attestation IDs are deleted.
        if (storedAttIdLen == 0) {
          // Ignore the SECOND_IMEI tag if the previous Applet's KeyMint version is less than
          // 3.0 and no SECOND_IMEI is provisioned.
          if (!(kmDataStore.ignoreSecondImei
              && attTags[index] == KMType.ATTESTATION_ID_SECOND_IMEI)) {
            KMException.throwIt(KMError.CANNOT_ATTEST_IDS);
          }
        } else {
          // Return INVALID_TAG if Attestation IDs does not match.
          if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length())
              || (0
                  != Util.arrayCompare(
                      scratchPad,
                      (short) 0,
                      KMByteBlob.cast(attIdTagValue).getBuffer(),
                      KMByteBlob.cast(attIdTagValue).getStartOff(),
                      storedAttIdLen))) {
            KMException.throwIt(KMError.CANNOT_ATTEST_IDS);
          }
          short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen);
          cert.extensionTag(KMByteTag.instance(attTags[index], blob), true);
        }
      }
      index++;
    }
  }

  private void processDestroyAttIdsCmd(APDU apdu) {
    kmDataStore.deleteAttestationIds();
    sendResponse(apdu, KMError.OK);
  }

  private void processVerifyAuthorizationCmd(APDU apdu) {
    sendResponse(apdu, KMError.UNIMPLEMENTED);
  }

  private short abortOperationCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 1);
    KMArray.cast(cmd).add((short) 0, KMInteger.exp());
    return receiveIncoming(apdu, cmd);
  }

  private void processAbortOperationCmd(APDU apdu) {
    short cmd = abortOperationCmd(apdu);
    data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
    KMOperationState op = findOperation(data[OP_HANDLE]);
    if (op == null) {
      sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE);
    } else {
      releaseOperation(op);
      sendResponse(apdu, KMError.OK);
    }
  }

  private short finishOperationCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 6);
    KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data
    KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature
    short authToken = KMHardwareAuthToken.exp();
    KMArray.cast(cmd).add((short) 3, authToken); // auth token
    short verToken = KMVerificationToken.exp();
    KMArray.cast(cmd).add((short) 4, verToken); // time stamp token
    KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token
    return receiveIncoming(apdu, cmd);
  }

  private void processFinishOperationCmd(APDU apdu) {
    short cmd = finishOperationCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
    data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
    data[SIGNATURE] = KMArray.cast(cmd).get((short) 2);
    data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3);
    data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4);
    data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5);
    // Check Operation Handle
    KMOperationState op = findOperation(data[OP_HANDLE]);
    if (op == null) {
      KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
    }
    // Authorize the finish operation
    authorizeUpdateFinishOperation(op, scratchPad);
    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;
      case KMType.AGREE_KEY:
        finishKeyAgreementOperation(op, scratchPad);
        break;
    }
    if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) {
      data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
    }
    // Remove the operation handle
    releaseOperation(op);

    // make response
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]);
    sendOutgoing(apdu, resp);
  }

  private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) {
    if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) {
      KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
    }
    finishAesDesOperation(op);
  }

  private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) {
    short len = KMByteBlob.cast(data[INPUT_DATA]).length();
    switch (op.getAlgorithm()) {
      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.getOperation()
                .finish(
                    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:
      case KMType.DES:
        finishAesDesOperation(op);
        break;
    }
  }

  private void finishAesDesOperation(KMOperationState op) {
    short len = KMByteBlob.cast(data[INPUT_DATA]).length();
    short blockSize = AES_BLOCK_SIZE;
    if (op.getAlgorithm() == KMType.DES) {
      blockSize = DES_BLOCK_SIZE;
    }

    if (op.getPurpose() == KMType.DECRYPT
        && len > 0
        && (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC)
        && ((short) (len % blockSize) != 0)) {
      KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
    }

    if (op.getBlockMode() == KMType.GCM) {
      if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) {
        KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
      }
      if (op.isAesGcmUpdateAllowed()) {
        op.setAesGcmUpdateComplete();
      }
      // Get the output size
      len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8));
    }
    // If padding i.e. pkcs7 then add padding to right
    // Output data can at most one block size more the input data in case of pkcs7 encryption
    // In case of gcm we will allocate extra memory of the size equal to blocksize.
    data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize));
    try {
      len =
          op.getOperation()
              .finish(
                  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());
    } catch (CryptoException e) {
      if (e.getReason() == CryptoException.ILLEGAL_USE) {
        // As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length
        // output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is
        // provided via update() method. So ignore this exception in case if all below conditions
        // are satisfied and simply return empty output.
        // 1. padding mode is PADDING_NONE.
        // 2. No input  message is processed in update().
        // 3. Zero length input data is passed in finish operation.
        if ((op.getPadding() == KMType.PADDING_NONE)
            && !op.isInputMsgProcessed()
            && (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) {
          len = 0;
        } else {
          KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
        }
      }
    }
    KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len);
  }

  private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) {
    try {
      KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
      short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]);
      short len =
          op.getOperation()
              .finish(
                  KMByteBlob.cast(blob).getBuffer(),
                  KMByteBlob.cast(blob).getStartOff(),
                  KMByteBlob.cast(blob).length(),
                  scratchPad,
                  (short) 0);
      data[OUTPUT_DATA] = KMByteBlob.instance((short) 32);
      Util.arrayCopyNonAtomic(
          scratchPad,
          (short) 0,
          KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
          KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(),
          len);
    } catch (CryptoException e) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }
  }

  private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) {
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
    switch (op.getAlgorithm()) {
      case KMType.RSA:
        // If there is no padding we can treat signing as a RSA decryption operation.
        try {
          if (op.getPurpose() == KMType.SIGN) {
            // len of signature will be 256 bytes - but it can be less then 256 bytes
            short len =
                op.getOperation()
                    .sign(
                        KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
                        KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
                        KMByteBlob.cast(data[INPUT_DATA]).length(),
                        scratchPad,
                        (short) 0);
            // Maximum output size of signature is 256 bytes. - the signature will always be
            // positive
            data[OUTPUT_DATA] = KMByteBlob.instance((short) 256);
            Util.arrayCopyNonAtomic(
                scratchPad,
                (short) 0,
                KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
                (short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len),
                len);
          } else {
            KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          }
        } catch (CryptoException e) {
          KMException.throwIt(KMError.INVALID_ARGUMENT);
        }
        break;
      case KMType.EC:
        short len = KMByteBlob.cast(data[INPUT_DATA]).length();
        // If DIGEST NONE then truncate the input data to 32 bytes.
        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.getOperation()
                  .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 {
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
        }
        break;
      case KMType.HMAC:
        // As per Keymaster HAL documentation, the length of the Hmac output can
        // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no
        // such provision to control the length of the Hmac output using JavaCard
        // crypto APIs and the current implementation always returns 32 bytes
        // length of Hmac output. So to provide support to TAG_MAC_LENGTH
        // feature, we truncate the output signature to TAG_MAC_LENGTH and return
        // the truncated signature back to the caller. At the time of verfication
        // we again compute the signature of the plain text input, truncate it to
        // TAG_MAC_LENGTH and compare it with the input signature for
        // verification.
        op.getOperation()
            .sign(
                KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
                KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
                KMByteBlob.cast(data[INPUT_DATA]).length(),
                scratchPad,
                (short) 0);
        if (op.getPurpose() == KMType.SIGN) {
          // Copy only signature of mac length size.
          data[OUTPUT_DATA] =
              KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8));
        } else if (op.getPurpose() == KMType.VERIFY) {
          if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8))
              || KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) {
            KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
          }
          if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) {
            KMException.throwIt(KMError.INVALID_MAC_LENGTH);
          }

          if (0
              != Util.arrayCompare(
                  scratchPad,
                  (short) 0,
                  KMByteBlob.cast(data[SIGNATURE]).getBuffer(),
                  KMByteBlob.cast(data[SIGNATURE]).getStartOff(),
                  KMByteBlob.cast(data[SIGNATURE]).length())) {
            KMException.throwIt(KMError.VERIFICATION_FAILED);
          }
          data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
        }
        break;
      default: // This is should never happen
        KMException.throwIt(KMError.OPERATION_CANCELLED);
        break;
    }
  }

  private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) {
    // If one time user Authentication is required
    if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) {
      // Validate Verification Token.
      validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad);
      // validate operation handle.
      short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge();
      if (KMInteger.compare(ptr, op.getHandle()) != 0) {
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
      tmpVariables[0] = op.getAuthTime();
      tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp();
      if (tmpVariables[2] == KMType.INVALID_VALUE) {
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
      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
      if (!validateHwToken(data[HW_TOKEN], scratchPad)) {
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
      tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge();
      if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) {
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
      short len = op.getAuthType(scratchPad, (short) 0);
      if (!authTokenMatches(op.getUserSecureId(), scratchPad, (short) 0, len, scratchPad, len)) {
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
    }
  }

  private void authorizeKeyUsageForCount(byte[] scratchPad) {
    short scratchPadOff = 0;
    Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0);

    short usageLimitBufLen =
        KMIntegerTag.getValue(
            scratchPad,
            scratchPadOff,
            KMType.UINT_TAG,
            KMType.MAX_USES_PER_BOOT,
            data[HW_PARAMETERS]);

    if (usageLimitBufLen == KMType.INVALID_VALUE) {
      return;
    }

    if (usageLimitBufLen > 4) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }

    if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) {
      // Get current counter, update and increment it.
      short len =
          kmDataStore.getRateLimitedKeyCount(
              data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4));
      if (len != 4) {
        KMException.throwIt(KMError.UNKNOWN_ERROR);
      }
      if (0
          >= KMInteger.unsignedByteArrayCompare(
              scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) {
        KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED);
      }
      // Increment the counter.
      Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0);
      Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1);
      KMUtils.add(
          scratchPad,
          scratchPadOff,
          (short) (scratchPadOff + len),
          (short) (scratchPadOff + len * 2));

      kmDataStore.setRateLimitedKeyCount(
          data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len);
    } else {
      // Persist auth tag.
      if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) {
        KMException.throwIt(KMError.TOO_MANY_OPERATIONS);
      }
    }
  }

  private void authorizeDeviceUnlock(byte[] scratchPad) {
    // 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 && kmDataStore.getDeviceLock()) {
      if (data[HW_TOKEN] == KMType.INVALID_VALUE) {
        KMException.throwIt(KMError.DEVICE_LOCKED);
      }
      ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp();
      // Check if the current auth time stamp is greater than device locked time stamp
      short ts = kmDataStore.getDeviceTimeStamp();
      if (KMInteger.compare(ptr, ts) <= 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.
      scratchPad[0] = KMType.PASSWORD;
      short authTypeLen = 1;
      if (kmDataStore.getDeviceLockPasswordOnly()) {
        if (!hwAuthTypeMatches(scratchPad, (short) 0, authTypeLen, scratchPad, authTypeLen)) {
          KMException.throwIt(KMError.DEVICE_LOCKED);
        }
      }
      // Unlock the device
      // repository.deviceLockedFlag = false;
      kmDataStore.setDeviceLock(false);
      kmDataStore.clearDeviceLockTimeStamp();
    }
  }

  private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) {
    // concatenation length will be 37 + length of verified parameters list - which
    // is typically empty
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
    // Add "Auth Verification" - 17 bytes.
    Util.arrayCopyNonAtomic(
        authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length);
    short len = (short) authVerification.length;
    // concatenate challenge - 8 bytes
    short ptr = KMVerificationToken.cast(verToken).getChallenge();
    KMInteger.cast(ptr)
        .value(
            scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;
    // concatenate timestamp -8 bytes
    ptr = KMVerificationToken.cast(verToken).getTimestamp();
    KMInteger.cast(ptr)
        .value(
            scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;
    // concatenate security level - 4 bytes
    scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT;
    len += KMInteger.UINT_32;
    // hmac the data
    ptr = KMVerificationToken.cast(verToken).getMac();

    return seProvider.hmacVerify(
        kmDataStore.getComputedHmacKey(),
        scratchPad,
        (short) 0,
        len,
        KMByteBlob.cast(ptr).getBuffer(),
        KMByteBlob.cast(ptr).getStartOff(),
        KMByteBlob.cast(ptr).length());
  }

  private void validateVerificationToken(short verToken, byte[] scratchPad) {
    short ptr = KMVerificationToken.cast(verToken).getMac();
    // If mac length is zero then token is empty.
    if (KMByteBlob.cast(ptr).length() == 0) {
      KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
    }
    if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) {
      // Throw Exception if none of the combination works.
      KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
    }
  }

  private short updateOperationCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 4);
    // Arguments
    KMArray.cast(cmd).add((short) 0, KMInteger.exp());
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
    short authToken = KMHardwareAuthToken.exp();
    KMArray.cast(cmd).add((short) 2, authToken);
    short verToken = KMVerificationToken.exp();
    KMArray.cast(cmd).add((short) 3, verToken);
    return receiveIncoming(apdu, cmd);
  }

  private void processUpdateOperationCmd(APDU apdu) {
    short cmd = updateOperationCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
    data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
    data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2);
    data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3);

    // Input data must be present even if it is zero length.
    if (data[INPUT_DATA] == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }

    // Check Operation Handle and get op state
    // Check Operation Handle
    KMOperationState op = findOperation(data[OP_HANDLE]);
    if (op == null) {
      KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
    }
    // authorize the update operation
    authorizeUpdateFinishOperation(op, scratchPad);

    if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) {
      // update the data.
      op.getOperation()
          .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;
    } else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) {
      // 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() == KMType.RSA) {
        KMException.throwIt(KMError.OPERATION_CANCELLED);
      }
      short len = KMByteBlob.cast(data[INPUT_DATA]).length();
      short blockSize = DES_BLOCK_SIZE;
      if (op.getAlgorithm() == KMType.AES) {
        blockSize = AES_BLOCK_SIZE;
        if (op.getBlockMode() == KMType.GCM) {
          // if input data present
          if (len > 0) {
            // no more future updateAAD allowed if input data present.
            if (op.isAesGcmUpdateAllowed()) {
              op.setAesGcmUpdateComplete();
            }
          }
        }
      }
      // Allocate output buffer as input data is already block aligned
      data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize));
      // Otherwise just update the data.
      // HAL consumes all the input and maintains a buffered data inside it. So the
      // applet sends the inputConsumed length as same as the input length.
      try {
        len =
            op.getOperation()
                .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());
      } catch (CryptoException e) {
        KMException.throwIt(KMError.INVALID_TAG);
      }
      if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) {
        // This flag is used to denote that an input data of length > 0 is received and processed
        // successfully in update command. This flag is later used in the finish operation
        // to handle a particular use case, where a zero length input data on AES/DES algorithm
        // with PADDING_NONE should return a zero length output with OK response.
        op.setProcessedInputMsg(true);
      }
      // Adjust the Output data if it is not equal to input data.
      // This happens in case of JCardSim provider.
      KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len);
    }

    if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) {
      data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
    }
    // Persist if there are any updates.
    // op.persist();
    // make response
    short resp = KMArray.instance((short) 2);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]);
    sendOutgoing(apdu, resp);
  }

  private short updateAadOperationCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 4);
    KMArray.cast(cmd).add((short) 0, KMInteger.exp());
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
    short authToken = KMHardwareAuthToken.exp();
    KMArray.cast(cmd).add((short) 2, authToken);
    short verToken = KMVerificationToken.exp();
    KMArray.cast(cmd).add((short) 3, verToken);
    return receiveIncoming(apdu, cmd);
  }

  private void processUpdateAadOperationCmd(APDU apdu) {
    short cmd = updateAadOperationCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
    data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
    data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2);
    data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3);

    // Input data must be present even if it is zero length.
    if (data[INPUT_DATA] == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.INVALID_ARGUMENT);
    }
    // Check Operation Handle and get op state
    // Check Operation Handle
    KMOperationState op = findOperation(data[OP_HANDLE]);
    if (op == null) {
      KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
    }
    if (op.getAlgorithm() != KMType.AES) {
      KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM);
    }
    if (op.getBlockMode() != KMType.GCM) {
      KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE);
    }
    if (!op.isAesGcmUpdateAllowed()) {
      KMException.throwIt(KMError.INVALID_TAG);
    }
    if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) {
      KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
    }
    // authorize the update operation
    authorizeUpdateFinishOperation(op, scratchPad);
    try {
      op.getOperation()
          .updateAAD(
              KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
              KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
              KMByteBlob.cast(data[INPUT_DATA]).length());
    } catch (CryptoException exp) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    // make response
    short resp = KMArray.instance((short) 1);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    sendOutgoing(apdu, resp);
  }

  private short beginOperationCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 4);
    // Arguments
    short params = KMKeyParameters.expAny();
    KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE));
    KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
    KMArray.cast(cmd).add((short) 2, params);
    short authToken = KMHardwareAuthToken.exp();
    KMArray.cast(cmd).add((short) 3, authToken);
    return receiveIncoming(apdu, cmd);
  }

  private void processBeginOperationCmd(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = beginOperationCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    short purpose = KMArray.cast(cmd).get((short) 0);
    data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1);
    data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2);
    data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3);
    purpose = KMEnum.cast(purpose).getVal();
    // Check for app id and app data.
    data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]);
    data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]);
    // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired
    // function itself.
    if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) {
      KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
    }
    KMTag.assertPresence(
        data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM);
    short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]);
    // If Blob usage tag is present in key characteristics then it should be standalone.
    if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) {
      if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) {
        KMException.throwIt(KMError.UNSUPPORTED_TAG);
      }
    }

    // Generate a random number for operation handle
    short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE);
    generateUniqueOperationHandle(
        KMByteBlob.cast(buf).getBuffer(),
        KMByteBlob.cast(buf).getStartOff(),
        KMByteBlob.cast(buf).length());
    /* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */
    short opHandle =
        KMInteger.instance(
            KMByteBlob.cast(buf).getBuffer(),
            KMByteBlob.cast(buf).getStartOff(),
            KMByteBlob.cast(buf).length());
    KMOperationState op = reserveOperation(algorithm, opHandle);
    if (op == null) {
      KMException.throwIt(KMError.TOO_MANY_OPERATIONS);
    }
    data[OP_HANDLE] = op.getHandle();
    op.setPurpose((byte) purpose);
    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;
      case KMType.AGREE_KEY:
        beginKeyAgreementOperation(op);
        break;
      default:
        KMException.throwIt(KMError.UNIMPLEMENTED);
        break;
    }
    short iv = KMType.INVALID_VALUE;
    // 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
        && op.getBlockMode() != KMType.ECB) {
      iv = 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.
        short ivBlob = KMByteBlob.instance((short) 8);
        Util.arrayCopy(
            KMByteBlob.cast(data[IV]).getBuffer(),
            KMByteBlob.cast(data[IV]).getStartOff(),
            KMByteBlob.cast(ivBlob).getBuffer(),
            KMByteBlob.cast(ivBlob).getStartOff(),
            (short) 8);
        data[IV] = ivBlob;
      }
      KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV]));
    } else {
      iv = KMArray.instance((short) 0);
    }
    short macLen = 0;
    if (op.getMacLength() != KMType.INVALID_VALUE) {
      macLen = (short) (op.getMacLength() / 8);
    }
    short params = KMKeyParameters.instance(iv);
    short resp = KMArray.instance((short) 5);
    KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
    KMArray.cast(resp).add((short) 1, params);
    KMArray.cast(resp).add((short) 2, data[OP_HANDLE]);
    KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode()));
    KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen));
    sendOutgoing(apdu, resp);
  }

  private void authorizePurpose(KMOperationState op) {
    switch (op.getAlgorithm()) {
      case KMType.AES:
      case KMType.DES:
        if (op.getPurpose() == KMType.SIGN
            || op.getPurpose() == KMType.VERIFY
            || op.getPurpose() == KMType.AGREE_KEY) {
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
        }
        break;
      case KMType.EC:
        if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) {
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
        }
        break;
      case KMType.HMAC:
        if (op.getPurpose() == KMType.ENCRYPT
            || op.getPurpose() == KMType.DECRYPT
            || op.getPurpose() == KMType.AGREE_KEY) {
          KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
        }
        break;
      case KMType.RSA:
        if (op.getPurpose() == KMType.AGREE_KEY) {
          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.UNSUPPORTED_DIGEST);
      }
      param = KMEnumArrayTag.cast(param).get((short) 0);
      if (!KMEnumArrayTag.cast(digests).contains(param)) {
        KMException.throwIt(KMError.INCOMPATIBLE_DIGEST);
      }
      op.setDigest((byte) param);
    } else if (KMEnumArrayTag.contains(
        KMType.PADDING, KMType.RSA_PKCS1_1_5_SIGN, data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
    }
    short paramPadding =
        KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]);
    if (paramPadding != KMType.INVALID_VALUE) {
      if (KMEnumArrayTag.cast(paramPadding).length() != 1) {
        // TODO vts fails because it expects UNSUPPORTED_PADDING_MODE
        KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
      }
      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 && op.getPurpose() != KMType.AGREE_KEY)
            || !isDigestSupported(op.getAlgorithm(), op.getDigest())) {
          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.UNSUPPORTED_PADDING_MODE);
      }
      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);
        }
        if (op.getPurpose() == KMType.SIGN
            || op.getPurpose() == KMType.VERIFY
            || param == KMType.RSA_OAEP) {
          // Digest is mandatory in these cases.
          if (!isDigestSupported(op.getAlgorithm(), op.getDigest())) {
            KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
          }
        }
        if (param == KMType.RSA_OAEP) {
          short mgfDigest =
              KMKeyParameters.findTag(
                  KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[KEY_PARAMETERS]);
          if (mgfDigest != KMType.INVALID_VALUE) {
            if (KMEnumArrayTag.cast(mgfDigest).length() != 1) {
              KMException.throwIt(KMError.INVALID_ARGUMENT);
            }
            mgfDigest = KMEnumArrayTag.cast(mgfDigest).get((short) 0);
            if (mgfDigest == KMType.DIGEST_NONE) {
              KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST);
            }

          } else {
            mgfDigest = KMType.SHA1;
          }
          short mgfDigestHwParams =
              KMKeyParameters.findTag(
                  KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[HW_PARAMETERS]);
          if ((mgfDigestHwParams != KMType.INVALID_VALUE)
              && (!KMEnumArrayTag.cast(mgfDigestHwParams).contains(mgfDigest))) {
            KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST);
          }
          if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) {
            KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST);
          }
          op.setMgfDigest((byte) mgfDigest);
        }
        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.UNSUPPORTED_BLOCK_MODE);
      }
      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:
        // Validate the block mode.
        switch (param) {
          case KMType.ECB:
          case KMType.CBC:
          case KMType.CTR:
          case KMType.GCM:
            break;
          default:
            KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE);
        }
        if (param == KMType.GCM) {
          if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) {
            KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
          }
          if (macLen == KMType.INVALID_VALUE) {
            KMException.throwIt(KMError.MISSING_MAC_LENGTH);
          }
          short minMacLen =
              KMIntegerTag.getShortValue(
                  KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]);
          if (minMacLen == KMType.INVALID_VALUE) {
            KMException.throwIt(KMError.INVALID_KEY_BLOB);
          }
          if (macLen % 8 != 0
              || macLen > MAX_GCM_TAG_LENGTH_BITS
              || macLen < MIN_GCM_TAG_LENGTH_BITS) {
            KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
          }
          if (macLen < minMacLen) {
            KMException.throwIt(KMError.INVALID_MAC_LENGTH);
          }
          op.setMacLength(macLen);
        }
        if (param == KMType.CTR) {
          if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) {
            KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
          }
        }
        break;
      case KMType.DES:
        // Validate the block mode.
        switch (param) {
          case KMType.ECB:
          case KMType.CBC:
            break;
          default:
            KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE);
        }
        if (param == KMType.INVALID_VALUE) {
          KMException.throwIt(KMError.INVALID_ARGUMENT);
        }
        break;
      case KMType.HMAC:
        short minMacLen =
            KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]);
        if (minMacLen == KMType.INVALID_VALUE) {
          KMException.throwIt(KMError.INVALID_KEY_BLOB);
        }
        op.setMinMacLength(minMacLen);
        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 % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) {
            KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
          }
          if (macLen < minMacLen) {
            KMException.throwIt(KMError.INVALID_MAC_LENGTH);
          }
          op.setMacLength(macLen);
        }
        break;
      default:
        break;
    }
    op.setBlockMode((byte) param);
  }

  private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) {
    authorizePurpose(op);
    authorizeDigest(op);
    authorizePadding(op);
    authorizeBlockModeAndMacLength(op);
    if (!validateHwToken(data[HW_TOKEN], scratchPad)) {
      data[HW_TOKEN] = KMType.INVALID_VALUE;
    }
    authorizeUserSecureIdAuthTimeout(op, scratchPad);
    authorizeDeviceUnlock(scratchPad);
    authorizeKeyUsageForCount(scratchPad);

    KMTag.assertAbsence(
        data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB);

    // Validate early boot
    // VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is
    // present
    if (kmDataStore.getEarlyBootEndedStatus()) {
      KMTag.assertAbsence(
          data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED);
    }

    // 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;

    if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE)
        && KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE)
        && op.getPurpose() != KMType.DECRYPT) {
      KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED);
    }

    short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]);
    // If Nonce is present then check whether the size of nonce is correct.
    if (nonce != KMType.INVALID_VALUE) {
      data[IV] = KMByteTag.cast(nonce).getValue();
      // For CBC mode - iv must be 8 bytes
      if (op.getBlockMode() == KMType.CBC
          && op.getAlgorithm() == KMType.DES
          && KMByteBlob.cast(data[IV]).length() != 8) {
        KMException.throwIt(KMError.INVALID_NONCE);
      }

      // For GCM mode - IV must be 12 bytes
      if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) {
        KMException.throwIt(KMError.INVALID_NONCE);
      }

      // For AES CBC and CTR modes IV must be 16 bytes
      if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR)
          && op.getAlgorithm() == KMType.AES
          && KMByteBlob.cast(data[IV]).length() != 16) {
        KMException.throwIt(KMError.INVALID_NONCE);
      }
    } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) {

      // For symmetric decryption iv is required
      if (op.getPurpose() == KMType.DECRYPT
          && (op.getBlockMode() == KMType.CBC
              || op.getBlockMode() == KMType.GCM
              || op.getBlockMode() == KMType.CTR)) {
        KMException.throwIt(KMError.MISSING_NONCE);
      } else if (op.getBlockMode() == KMType.ECB) {
        // For ECB we create zero length nonce
        data[IV] = KMByteBlob.instance((short) 0);
      } else if (op.getPurpose() == KMType.ENCRYPT) {

        // For encrypt mode if nonce is absent then create random nonce of correct length
        byte ivLen = 16;
        if (op.getBlockMode() == KMType.GCM) {
          ivLen = 12;
        } else if (op.getAlgorithm() == KMType.DES) {
          ivLen = 8;
        }
        data[IV] = KMByteBlob.instance(ivLen);
        seProvider.newRandomNumber(
            KMByteBlob.cast(data[IV]).getBuffer(),
            KMByteBlob.cast(data[IV]).getStartOff(),
            KMByteBlob.cast(data[IV]).length());
      }
    }
  }

  private void beginKeyAgreementOperation(KMOperationState op) {
    if (op.getAlgorithm() != KMType.EC) {
      KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
    }

    op.setOperation(
        seProvider.initAsymmetricOperation(
            (byte) op.getPurpose(),
            (byte) op.getAlgorithm(),
            (byte) op.getPadding(),
            (byte) op.getDigest(),
            KMType.DIGEST_NONE, /* No MGF1 Digest */
            KMByteBlob.cast(data[SECRET]).getBuffer(),
            KMByteBlob.cast(data[SECRET]).getStartOff(),
            KMByteBlob.cast(data[SECRET]).length(),
            null,
            (short) 0,
            (short) 0));
  }

  private void beginCipherOperation(KMOperationState op) {
    switch (op.getAlgorithm()) {
      case KMType.RSA:
        try {
          if (op.getPurpose() == KMType.DECRYPT) {
            op.setOperation(
                seProvider.initAsymmetricOperation(
                    (byte) op.getPurpose(),
                    (byte) op.getAlgorithm(),
                    (byte) op.getPadding(),
                    (byte) op.getDigest(),
                    (byte) op.getMgfDigest(),
                    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 {
            KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          }
        } catch (CryptoException exp) {
          KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
        }
        break;
      case KMType.AES:
      case KMType.DES:
        if (op.getBlockMode() == KMType.GCM) {
          op.setAesGcmUpdateStart();
        }
        try {
          op.setOperation(
              seProvider.initSymmetricOperation(
                  (byte) op.getPurpose(),
                  (byte) op.getAlgorithm(),
                  (byte) op.getDigest(),
                  (byte) op.getPadding(),
                  (byte) op.getBlockMode(),
                  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(),
                  op.getMacLength()));
        } 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);
          }
        }
    }
  }

  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) {

      op.setTrustedConfirmationSigner(
          seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey()));

      op.getTrustedConfirmationSigner()
          .update(confirmationToken, (short) 0, (short) confirmationToken.length);
    }
  }

  private void beginSignVerifyOperation(KMOperationState op) {
    switch (op.getAlgorithm()) {
      case KMType.RSA:
        try {
          if (op.getPurpose() == KMType.SIGN) {
            op.setOperation(
                seProvider.initAsymmetricOperation(
                    (byte) op.getPurpose(),
                    (byte) op.getAlgorithm(),
                    (byte) op.getPadding(),
                    (byte) op.getDigest(),
                    KMType.DIGEST_NONE, /* No MGF Digest */
                    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 {
            KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          }
        } catch (CryptoException exp) {
          KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
        }
        break;
      case KMType.EC:
        try {
          if (op.getPurpose() == KMType.SIGN) {
            op.setOperation(
                seProvider.initAsymmetricOperation(
                    (byte) op.getPurpose(),
                    (byte) op.getAlgorithm(),
                    (byte) op.getPadding(),
                    (byte) op.getDigest(),
                    KMType.DIGEST_NONE, /* No MGF Digest */
                    KMByteBlob.cast(data[SECRET]).getBuffer(),
                    KMByteBlob.cast(data[SECRET]).getStartOff(),
                    KMByteBlob.cast(data[SECRET]).length(),
                    null,
                    (short) 0,
                    (short) 0));
          } else {
            KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
          }
        } catch (CryptoException exp) {
          // Javacard does not support NO digest based signing.
          KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
        }
        break;
      case KMType.HMAC:
        // As per Keymaster HAL documentation, the length of the Hmac output can
        // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no
        // such provision to control the length of the Hmac output using JavaCard
        // crypto APIs and the current implementation always returns 32 bytes
        // length of Hmac output. So to provide support to TAG_MAC_LENGTH
        // feature, we truncate the output signature to TAG_MAC_LENGTH and return
        // the truncated signature back to the caller. At the time of verfication
        // we again compute the signature of the plain text input, truncate it to
        // TAG_MAC_LENGTH and compare it with the input signature for
        // verification. So this is the reason we are using KMType.SIGN directly
        // instead of using op.getPurpose().
        try {
          op.setOperation(
              seProvider.initSymmetricOperation(
                  (byte) KMType.SIGN,
                  (byte) op.getAlgorithm(),
                  (byte) op.getDigest(),
                  (byte) op.getPadding(),
                  (byte) op.getBlockMode(),
                  KMByteBlob.cast(data[SECRET]).getBuffer(),
                  KMByteBlob.cast(data[SECRET]).getStartOff(),
                  KMByteBlob.cast(data[SECRET]).length(),
                  null,
                  (short) 0,
                  (short) 0,
                  (short) 0));
        } catch (CryptoException exp) {
          KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
        }
        break;
      default:
        KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
        break;
    }
  }

  private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) {
    short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId();
    if (!KMInteger.cast(secureUserId).isZero()) {
      if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) {
        return true;
      }
    }

    short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId();
    if (!KMInteger.cast(authenticatorId).isZero()) {
      if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) {
        return true;
      }
    }
    return false;
  }

  public boolean hwAuthTypeMatches(
      byte[] buf, short off, short len, byte[] scratchPad, short scratchOff) {
    Util.arrayFillNonAtomic(scratchPad, scratchOff, (short) (2 * KMInteger.UINT_32), (byte) 0);
    short enumPtr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType();
    if (KMInteger.UINT_32 != KMEnum.cast(enumPtr).value(scratchPad, scratchOff)) {
      return false;
    }
    Util.arrayCopyNonAtomic(
        buf, off, scratchPad, (short) (scratchOff + 2 * KMInteger.UINT_32 - len), len);
    short highShort = Util.getShort(scratchPad, scratchOff);
    short lowShort = Util.getShort(scratchPad, (short) (scratchOff + 2));
    short otherHighShort = Util.getShort(scratchPad, (short) (scratchOff + KMInteger.UINT_32));
    short otherLowShort = Util.getShort(scratchPad, (short) (scratchOff + KMInteger.UINT_32 + 2));
    return (0 != (lowShort & otherLowShort) || 0 != (highShort & otherHighShort));
  }

  private boolean authTokenMatches(
      short userSecureIdsPtr,
      byte[] buf,
      short off,
      short len,
      byte[] scratchPad,
      short scratchOff) {
    if (data[HW_TOKEN] == KMType.INVALID_VALUE) {
      return false;
    }
    if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) {
      return false;
    }
    // check auth type
    return hwAuthTypeMatches(buf, off, len, scratchPad, scratchOff);
  }

  private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) {
    short authTime;
    short authType;
    // Authorize User Secure Id and Auth timeout
    short userSecureIdPtr =
        KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]);
    if (userSecureIdPtr != KMType.INVALID_VALUE) {
      // Authentication required.
      if (KMType.INVALID_VALUE
          != KMKeyParameters.findTag(
              KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) {
        // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
      }
      // authenticator type must be provided.
      if (KMType.INVALID_VALUE
          == (authType =
              KMKeyParameters.findTag(
                  KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) {
        // Authentication required, but no auth type found.
        KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
      }
      short len = KMEnumTag.cast(authType).value(scratchPad, (short) 0);

      short authTimeoutTagPtr =
          KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]);
      if (authTimeoutTagPtr != KMType.INVALID_VALUE) {
        // authenticate user
        if (!authTokenMatches(userSecureIdPtr, scratchPad, (short) 0, len, scratchPad, len)) {
          KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
        }

        authTimeoutTagPtr =
            KMKeyParameters.findTag(
                KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]);
        if (authTimeoutTagPtr == KMType.INVALID_VALUE) {
          KMException.throwIt(KMError.INVALID_KEY_BLOB);
        }
        authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue();
        // 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(), scratchPad);
        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
        // store user secure id and authType in OperationState.
        op.setUserSecureId(userSecureIdPtr);
        op.setAuthType(scratchPad, (short) 0, len);
        // set flags
        op.setOneTimeAuthReqd(false);
        op.setAuthPerOperationReqd(true);
      }
    }
  }

  private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) {
    // The challenge, userId and authenticatorId, authenticatorType and timestamp
    // are in network order (big-endian).
    short len = 0;
    // 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) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;
    // concatenate user id - 8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getUserId();
    KMInteger.cast(ptr)
        .value(
            scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;
    // concatenate authenticator id - 8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId();
    KMInteger.cast(ptr)
        .value(
            scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;
    // concatenate authenticator type - 4 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType();
    KMEnum.cast(ptr).value(scratchPad, len);
    len += KMInteger.UINT_32;
    // concatenate timestamp -8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp();
    KMInteger.cast(ptr)
        .value(
            scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;

    ptr = KMHardwareAuthToken.cast(hwToken).getMac();

    return seProvider.hmacVerify(
        kmDataStore.getComputedHmacKey(),
        scratchPad,
        (short) 0,
        len,
        KMByteBlob.cast(ptr).getBuffer(),
        KMByteBlob.cast(ptr).getStartOff(),
        KMByteBlob.cast(ptr).length());
  }

  private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) {
    // The challenge, userId and authenticatorId values are in little endian order,
    // but authenticatorType and timestamp are in network order (big-endian).
    short len = 0;
    // 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).toLittleEndian(scratchPad, len);
    len += KMInteger.UINT_64;
    // concatenate user id - 8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getUserId();
    KMInteger.cast(ptr).toLittleEndian(scratchPad, len);
    len += KMInteger.UINT_64;
    // concatenate authenticator id - 8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId();
    KMInteger.cast(ptr).toLittleEndian(scratchPad, len);
    len += KMInteger.UINT_64;
    // concatenate authenticator type - 4 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType();
    KMEnum.cast(ptr).value(scratchPad, len);
    len += KMInteger.UINT_32;
    // concatenate timestamp - 8 bytes
    ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp();
    KMInteger.cast(ptr)
        .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length())));
    len += KMInteger.UINT_64;

    ptr = KMHardwareAuthToken.cast(hwToken).getMac();

    return seProvider.hmacVerify(
        kmDataStore.getComputedHmacKey(),
        scratchPad,
        (short) 0,
        len,
        KMByteBlob.cast(ptr).getBuffer(),
        KMByteBlob.cast(ptr).getStartOff(),
        KMByteBlob.cast(ptr).length());
  }

  private boolean validateHwToken(short hwToken, byte[] scratchPad) {
    // CBOR Encoding is always big endian
    short ptr = KMHardwareAuthToken.cast(hwToken).getMac();
    // If mac length is zero then token is empty.
    if (KMByteBlob.cast(ptr).length() == 0) {
      return false;
    }
    if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) {
      return verifyHwTokenMacInLittleEndian(hwToken, scratchPad);
    } else {
      return verifyHwTokenMacInBigEndian(hwToken, scratchPad);
    }
  }

  private short importKeyCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 6);
    // Arguments
    short params = KMKeyParameters.expAny();
    KMArray.cast(cmd).add((short) 0, params);
    KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT));
    KMArray.cast(cmd).add((short) 2, KMByteBlob.exp());
    KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key
    KMArray.cast(cmd).add((short) 4, params); // attest key params
    KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer
    return receiveIncoming(apdu, cmd);
  }

  private void processImportKeyCmd(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = importKeyCmd(apdu);
    byte[] scratchPad = apdu.getBuffer();
    data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0);
    short keyFmt = KMArray.cast(cmd).get((short) 1);
    data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2);
    data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3);
    data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4);
    data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5);
    keyFmt = KMEnum.cast(keyFmt).getVal();

    data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty.
    data[ORIGIN] = KMType.IMPORTED;
    importKey(apdu, keyFmt, scratchPad);
  }

  private void validateImportKey(short params, short keyFmt) {
    short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, params);
    // ATTEST_KEY cannot be combined with any other purpose.
    if (attKeyPurpose != KMType.INVALID_VALUE
        && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)
        && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) {
      KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
    }
    // Rollback protection not supported
    KMTag.assertAbsence(
        params,
        KMType.BOOL_TAG,
        KMType.ROLLBACK_RESISTANCE,
        KMError.ROLLBACK_RESISTANCE_UNAVAILABLE);
    // As per specification, Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is
    // provided to IKeyMintDevice::importKey
    KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED);
    // Check if the tags are supported.
    if (KMKeyParameters.hasUnsupportedTags(params)) {
      KMException.throwIt(KMError.UNSUPPORTED_TAG);
    }
    // Algorithm must be present
    KMTag.assertPresence(params, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT);
    short alg = KMEnumTag.getValue(KMType.ALGORITHM, params);
    // key format must be raw if aes, des or hmac and pkcs8 for rsa and ec.
    if ((alg == KMType.AES || alg == KMType.DES || alg == KMType.HMAC) && keyFmt != KMType.RAW) {
      KMException.throwIt(KMError.UNIMPLEMENTED);
    }
    if ((alg == KMType.RSA || alg == KMType.EC) && keyFmt != KMType.PKCS8) {
      KMException.throwIt(KMError.UNIMPLEMENTED);
    }
  }

  private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) {
    validateImportKey(data[KEY_PARAMETERS], keyFmt);
    // Check algorithm and dispatch to appropriate handler.
    short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
    switch (alg) {
      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;
    }
    makeKeyCharacteristics(scratchPad);
    KMAttestationCert cert =
        generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad);
    createEncryptedKeyBlob(scratchPad);
    sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]);
  }

  private void importECKeys(byte[] scratchPad) {
    // Decode key material
    KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
    short keyBlob = pkcs8.decodeEc(data[IMPORTED_KEY_BLOB]);
    data[PUB_KEY] = KMArray.cast(keyBlob).get((short) 0);
    data[SECRET] = KMArray.cast(keyBlob).get((short) 1);
    // initialize 256 bit p256 key for given private key and public key.
    short index = 0;
    // check whether the key size tag is present in key parameters.
    short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8);
    short keySize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    if (keySize != KMType.INVALID_VALUE) {
      // As per NIST.SP.800-186 page 9,  secret for 256 curve should be between
      // 256-383
      if (((256 <= SecretLen) && (383 >= SecretLen)) ^ keySize == 256) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
      if (keySize != 256) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
    } else {
      if ((256 > SecretLen) || (383 < SecretLen)) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      // add the key size to scratchPad
      keySize = KMInteger.uint_16((short) 256);
      keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keySize);
      Util.setShort(scratchPad, index, keySize);
      index += 2;
    }
    // check the curve if present in key parameters.
    short curve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]);
    if (curve != KMType.INVALID_VALUE) {
      // As per NIST.SP.800-186 page 9,  secret length for 256 curve should be between
      // 256-383
      if (((256 <= SecretLen) && (383 >= SecretLen)) ^ curve == KMType.P_256) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
      if (curve != KMType.P_256) {
        KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE);
      }
    } else {
      if ((256 > SecretLen) || (383 < SecretLen)) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      // add the curve to scratchPad
      curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256);
      Util.setShort(scratchPad, index, curve);
      index += 2;
    }

    // Check whether key can be created
    seProvider.importAsymmetricKey(
        KMType.EC,
        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());

    // add scratch pad to key parameters
    updateKeyParameters(scratchPad, index);
    data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
  }

  private void importHmacKey(byte[] scratchPad) {
    // Get Key
    data[SECRET] = data[IMPORTED_KEY_BLOB];
    // create HMAC key of up to 512 bit
    short index = 0; // index in scratchPad for update params
    // check the keysize tag if present in key parameters.
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    if (keysize != KMType.INVALID_VALUE) {
      if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      if (keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
    } else {
      // add the key size to scratchPad
      keysize = (short) (KMByteBlob.length(data[SECRET]) * 8);
      if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      keysize = KMInteger.uint_16(keysize);
      short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
      Util.setShort(scratchPad, index, keySizeTag);
      index += 2;
    }
    // Check whether key can be created
    seProvider.importSymmetricKey(
        KMType.HMAC,
        keysize,
        KMByteBlob.cast(data[SECRET]).getBuffer(),
        KMByteBlob.cast(data[SECRET]).getStartOff(),
        KMByteBlob.cast(data[SECRET]).length());

    // update the key parameters list
    updateKeyParameters(scratchPad, index);
    // validate HMAC Key parameters
    validateHmacKey();
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  private void importTDESKey(byte[] scratchPad) {
    // Decode Key Material
    data[SECRET] = data[IMPORTED_KEY_BLOB];
    short index = 0; // index in scratchPad for update params
    // check the keysize tag if present in key parameters.
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    if (keysize != KMType.INVALID_VALUE) {
      if (keysize != 168) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      if (192 != (short) (8 * KMByteBlob.length(data[SECRET]))) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
    } else {
      keysize = (short) (KMByteBlob.length(data[SECRET]) * 8);
      if (keysize != 192) {
        KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
      }
      // add the key size to scratchPad
      keysize = KMInteger.uint_16((short) 168);
      short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
      Util.setShort(scratchPad, index, keysizeTag);
      index += 2;
    }
    // Read Minimum Mac length - it must not be present
    KMTag.assertAbsence(
        data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG);
    // Check whether key can be created
    seProvider.importSymmetricKey(
        KMType.DES,
        keysize,
        KMByteBlob.cast(data[SECRET]).getBuffer(),
        KMByteBlob.cast(data[SECRET]).getStartOff(),
        KMByteBlob.cast(data[SECRET]).length());
    // update the key parameters list
    updateKeyParameters(scratchPad, index);
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  private void validateAesKeySize(short keySizeBits) {
    if (keySizeBits != 128 && keySizeBits != 256) {
      KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
    }
  }

  private void importAESKey(byte[] scratchPad) {
    // Get Key
    data[SECRET] = data[IMPORTED_KEY_BLOB];
    // create 128 or 256 bit AES key
    short index = 0; // index in scratchPad for update params
    // check the keysize tag if present in key parameters.
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    if (keysize != KMType.INVALID_VALUE) {
      if (keysize != (short) (8 * KMByteBlob.length(data[SECRET]))) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
      validateAesKeySize(keysize);
    } else {
      // add the key size to scratchPad
      keysize = (short) (8 * KMByteBlob.cast(data[SECRET]).length());
      validateAesKeySize(keysize);
      keysize = KMInteger.uint_16(keysize);
      short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
      Util.setShort(scratchPad, index, keysizeTag);
      index += 2;
    }
    // Check whether key can be created
    seProvider.importSymmetricKey(
        KMType.AES,
        keysize,
        KMByteBlob.cast(data[SECRET]).getBuffer(),
        KMByteBlob.cast(data[SECRET]).getStartOff(),
        KMByteBlob.cast(data[SECRET]).length());

    // update the key parameters list
    updateKeyParameters(scratchPad, index);
    // validate AES Key parameters
    validateAESKey();
    data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
  }

  private void importRSAKey(byte[] scratchPad) {
    // Decode key material
    KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
    short keyblob = pkcs8.decodeRsa(data[IMPORTED_KEY_BLOB]);
    data[PUB_KEY] = KMArray.cast(keyblob).get((short) 0);
    short pubKeyExp = KMArray.cast(keyblob).get((short) 1);
    data[SECRET] = KMArray.cast(keyblob).get((short) 2);
    if (F4.length != KMByteBlob.cast(pubKeyExp).length()) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    if (Util.arrayCompare(
            F4,
            (short) 0,
            KMByteBlob.cast(pubKeyExp).getBuffer(),
            KMByteBlob.cast(pubKeyExp).getStartOff(),
            (short) F4.length)
        != 0) {
      KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
    }
    short index = 0; // index in scratchPad for update parameters.
    // validate public exponent if present in key params - it must be 0x010001
    short len =
        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 (len != KMTag.INVALID_VALUE) {
      if (len != 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);
      pubKeyExp = KMInteger.uint_32(scratchPad, (short) 10);
      pubKeyExp = KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, pubKeyExp);
      Util.setShort(scratchPad, index, pubKeyExp);
      index += 2;
    }

    // check the keysize tag if present in key parameters.
    short keysize =
        KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
    short kSize = (short) (KMByteBlob.length(data[PUB_KEY]) * 8);
    if (keysize != KMType.INVALID_VALUE) {
      if (keysize != 2048 || (keysize != kSize)) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
    } else {
      if (2048 != kSize) {
        KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
      }
      // add the key size to scratchPad
      keysize = KMInteger.uint_16((short) 2048);
      keysize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
      Util.setShort(scratchPad, index, keysize);
      index += 2;
    }

    // Check whether key can be created
    seProvider.importAsymmetricKey(
        KMType.RSA,
        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());

    // update the key parameters list
    updateKeyParameters(scratchPad, index);
    // validate RSA Key parameters
    validateRSAKey(scratchPad);
    data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
    KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
  }

  private void updateKeyParameters(byte[] newParams, short len) {
    if (len == 0) {
      return; // nothing to update
    }
    // Create Update Param array and copy current params
    short params = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals();
    len = (short) (KMArray.cast(params).length() + (short) (len / 2));
    short updatedParams = KMArray.instance(len); // update params

    len = KMArray.cast(params).length();
    short index = 0;

    // copy the existing key parameters to updated array
    while (index < len) {
      short tag = KMArray.cast(params).get(index);
      KMArray.cast(updatedParams).add(index, tag);
      index++;
    }

    // copy new parameters to updated array
    len = KMArray.cast(updatedParams).length();
    short newParamIndex = 0; // index in ptrArr
    while (index < len) {
      short tag = Util.getShort(newParams, newParamIndex);
      KMArray.cast(updatedParams).add(index, tag);
      index++;
      newParamIndex += 2;
    }
    // replace with updated key parameters.
    data[KEY_PARAMETERS] = KMKeyParameters.instance(updatedParams);
  }

  private short initStrongBoxCmd(APDU apdu) {
    short cmd = KMArray.instance((short) 3);
    KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // OS version
    KMArray.cast(cmd).add((short) 1, KMInteger.exp()); // OS patch level
    KMArray.cast(cmd).add((short) 2, KMInteger.exp()); // Vendor patch level
    return receiveIncoming(apdu, cmd);
  }

  // This command is executed to set the boot parameters.
  // releaseAllOperations has to be called on every boot, so
  // it is called from inside initStrongBoxCmd. Later in future if
  // initStrongBoxCmd is removed, then make sure that releaseAllOperations
  // is moved to a place where it is called on every boot.
  private void processInitStrongBoxCmd(APDU apdu) {
    short cmd = initStrongBoxCmd(apdu);

    short osVersion = KMArray.cast(cmd).get((short) 0);
    short osPatchLevel = KMArray.cast(cmd).get((short) 1);
    short vendorPatchLevel = KMArray.cast(cmd).get((short) 2);
    setOsVersion(osVersion);
    setOsPatchLevel(osPatchLevel);
    setVendorPatchLevel(vendorPatchLevel);
    kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS);
  }

  public void reboot() {
    // flag to maintain early boot ended state
    kmDataStore.setEarlyBootEndedStatus(false);
    // Clear all the operation state.
    releaseAllOperations();
    // Hmac is cleared, so generate a new Hmac nonce.
    initHmacNonceAndSeed();
    // Clear all auth tags.
    kmDataStore.removeAllAuthTags();
  }

  protected void setOsVersion(short version) {
    kmDataStore.setOsVersion(
        KMInteger.cast(version).getBuffer(),
        KMInteger.cast(version).getStartOff(),
        KMInteger.cast(version).length());
  }

  protected void setOsPatchLevel(short patch) {
    kmDataStore.setOsPatch(
        KMInteger.cast(patch).getBuffer(),
        KMInteger.cast(patch).getStartOff(),
        KMInteger.cast(patch).length());
  }

  protected void setVendorPatchLevel(short patch) {
    kmDataStore.setVendorPatchLevel(
        KMInteger.cast(patch).getBuffer(),
        KMInteger.cast(patch).getStartOff(),
        KMInteger.cast(patch).length());
  }

  private short generateKeyCmd(APDU apdu) {
    short params = KMKeyParameters.expAny();
    short blob = KMByteBlob.exp();
    // Array of expected arguments
    short cmd = KMArray.instance((short) 4);
    KMArray.cast(cmd).add((short) 0, params); // key params
    KMArray.cast(cmd).add((short) 1, blob); // attest key
    KMArray.cast(cmd).add((short) 2, params); // attest key params
    KMArray.cast(cmd).add((short) 3, blob); // issuer
    return receiveIncoming(apdu, cmd);
  }

  private void processGenerateKey(APDU apdu) {
    // Receive the incoming request fully from the host into buffer.
    short cmd = generateKeyCmd(apdu);
    // Re-purpose the apdu buffer as scratch pad.
    byte[] scratchPad = apdu.getBuffer();
    data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0);
    data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1);
    data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2);
    data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3);
    data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty.
    // ROLLBACK_RESISTANCE not supported.
    KMTag.assertAbsence(
        data[KEY_PARAMETERS],
        KMType.BOOL_TAG,
        KMType.ROLLBACK_RESISTANCE,
        KMError.ROLLBACK_RESISTANCE_UNAVAILABLE);

    // Algorithm must be present
    KMTag.assertPresence(
        data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT);

    // Check if the tags are supported.
    if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) {
      KMException.throwIt(KMError.UNSUPPORTED_TAG);
    }

    short attKeyPurpose =
        KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]);
    // ATTEST_KEY cannot be combined with any other purpose.
    if (attKeyPurpose != KMType.INVALID_VALUE
        && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)
        && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) {
      KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
    }
    short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
    // Check algorithm and dispatch to appropriate handler.
    switch (alg) {
      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 and associated attestation.
    data[ORIGIN] = KMType.GENERATED;
    makeKeyCharacteristics(scratchPad);
    // construct the certificate and place the encoded data in data[CERTIFICATE]
    KMAttestationCert cert =
        generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad);
    createEncryptedKeyBlob(scratchPad);
    sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]);
  }

  private short getApplicationId(short params) {
    short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, params);
    if (appId != KMTag.INVALID_VALUE) {
      appId = KMByteTag.cast(appId).getValue();
      if (KMByteBlob.cast(appId).length() == 0) {
        // Treat empty as INVALID.
        return KMType.INVALID_VALUE;
      }
    }
    return appId;
  }

  private short getApplicationData(short params) {
    short appData = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, params);
    if (appData != KMTag.INVALID_VALUE) {
      appData = KMByteTag.cast(appData).getValue();
      if (KMByteBlob.cast(appData).length() == 0) {
        // Treat empty as INVALID.
        return KMType.INVALID_VALUE;
      }
    }
    return appData;
  }

  private short getAttestationMode(short attKeyBlob, short attChallenge) {
    short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]);
    short mode = KMType.NO_CERT;
    if (KMEnumTag.cast(alg).getValue() != KMType.RSA
        && KMEnumTag.cast(alg).getValue() != KMType.EC) {
      return mode;
    }
    // If attestation keyblob preset
    if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) {
      // No attestation challenge present then it is an error
      if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) {
        KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING);
      } else {
        mode = KMType.ATTESTATION_CERT;
      }
    } else { // no attestation key blob
      // Attestation challenge present then it is an error because no factory provisioned attest key
      if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) {
        KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED);
      } else if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS])
          || KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) {
        // The Purpose value can be read from either data[HW_PARAMETERS] or data[KEY_PARAMETERS]
        // as the values will be same, and they are cryptographically bound.
        mode = KMType.SELF_SIGNED_CERT;
      } else {
        mode = KMType.FAKE_CERT;
      }
    }
    return mode;
  }

  private KMAttestationCert generateAttestation(
      short attKeyBlob, short attKeyParam, byte[] scratchPad) {
    // 1) If attestation key is present and attestation challenge is absent then it is an error.
    // 2) If attestation key is absent and attestation challenge is present then it is an error as
    // factory provisioned attestation key is not supported.
    // 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it
    // is an error.
    // 4) If the generated/imported keys are RSA or EC then validity period must be specified.
    // Device Unique Attestation is not supported.
    short heapStart = repository.getHeapIndex();
    KMTag.assertAbsence(
        data[KEY_PARAMETERS],
        KMType.BOOL_TAG,
        KMType.DEVICE_UNIQUE_ATTESTATION,
        KMError.CANNOT_ATTEST_IDS);
    // Read attestation challenge if present
    short attChallenge =
        KMKeyParameters.findTag(
            KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]);
    if (attChallenge != KMType.INVALID_VALUE) {
      attChallenge = KMByteTag.cast(attChallenge).getValue();
    }
    // No attestation required for symmetric keys
    short mode = getAttestationMode(attKeyBlob, attChallenge);
    KMAttestationCert cert = null;

    switch (mode) {
      case KMType.ATTESTATION_CERT:
        cert =
            makeAttestationCert(
                attKeyBlob, attKeyParam, attChallenge, data[ATTEST_KEY_ISSUER], scratchPad);
        break;
      case KMType.SELF_SIGNED_CERT:
        cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY], mode, scratchPad);
        break;
      case KMType.FAKE_CERT:
        // Generate certificate with no signature.
        cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], mode, scratchPad);
        break;
      default:
        data[CERTIFICATE] = KMType.INVALID_VALUE;
        return null;
    }
    // Certificate Data is converted to cbor and written to the end of the stack.
    short certData = repository.allocReclaimableMemory(MAX_CERT_SIZE);
    // Leave first 4 bytes for Array header and ByteBlob header.
    cert.buffer(repository.getHeap(), (short) (certData + 4), (short) (MAX_CERT_SIZE - 4));
    // Build the certificate - this will sign the cert
    cert.build();
    // Certificate is now built so the data in the heap starting from heapStart to the current
    // heap index can be reused. So resetting the heap index to heapStart.
    repository.setHeapIndex(heapStart);
    data[CERTIFICATE] = certData;
    return cert;
  }

  // Encodes KeyCharacteristics at the end of the heap
  private void encodeKeyCharacteristics(short keyChars) {
    byte[] buffer = repository.getHeap();
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short ptr = repository.allocReclaimableMemory(MAX_KEY_CHARS_SIZE);
    short len = encoder.encode(keyChars, buffer, ptr, prevReclaimIndex, MAX_KEY_CHARS_SIZE);
    // shift the encoded KeyCharacteristics data towards the right till the data[CERTIFICATE]
    // offset.
    Util.arrayCopyNonAtomic(buffer, ptr, buffer, (short) (ptr + (MAX_KEY_CHARS_SIZE - len)), len);
    // Reclaim the unused memory.
    repository.reclaimMemory((short) (MAX_KEY_CHARS_SIZE - len));
  }

  // Encodes KeyBlob at the end of the heap
  private void encodeKeyBlob(short keyBlobPtr) {
    // allocate reclaimable memory.
    byte[] buffer = repository.getHeap();
    short prevReclaimIndex = repository.getHeapReclaimIndex();
    short top = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE);
    short keyBlob = encoder.encode(keyBlobPtr, buffer, top, prevReclaimIndex, MAX_KEYBLOB_SIZE);
    Util.arrayCopyNonAtomic(
        repository.getHeap(),
        top,
        repository.getHeap(),
        (short) (top + MAX_KEYBLOB_SIZE - keyBlob),
        keyBlob);
    short newTop = (short) (top + MAX_KEYBLOB_SIZE - keyBlob);
    // Encode the KeyBlob array inside a ByteString. Get the length of
    // the ByteString header.
    short encodedBytesLength = encoder.getEncodedBytesLength(keyBlob);
    newTop -= encodedBytesLength;
    encoder.encodeByteBlobHeader(keyBlob, buffer, newTop, encodedBytesLength);
    // Reclaim unused memory.
    repository.reclaimMemory((short) (newTop - top));
  }

  private short readKeyBlobVersion(short keyBlob) {
    short version = KMType.INVALID_VALUE;
    try {
      version =
          decoder.readKeyblobVersion(
              KMByteBlob.cast(keyBlob).getBuffer(),
              KMByteBlob.cast(keyBlob).getStartOff(),
              KMByteBlob.cast(keyBlob).length());
      if (version == KMType.INVALID_VALUE) {
        // If Version is not present. Then it is either an old KeyBlob or
        // corrupted KeyBlob.
        version = 0;
      } else {
        version = KMInteger.cast(version).getShort();
        if (version > KEYBLOB_CURRENT_VERSION || version < 0) {
          KMException.throwIt(KMError.INVALID_KEY_BLOB);
        }
      }
    } catch (Exception e) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    return version;
  }

  private void readKeyBlobParams(short version, short parsedKeyBlob) {
    data[KEY_BLOB] = parsedKeyBlob;
    // initialize data
    switch (version) {
      case (short) 0:
        data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 0);
        data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 1);
        data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 2);
        data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 3);
        data[PUB_KEY] = KMType.INVALID_VALUE;
        if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V0) {
          data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 4);
        }
        // Set the data[KEY_BLOB_VERSION_DATA_OFFSET] with integer value of 0 so
        // that it will used at later point of time.
        data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_8((byte) 0);
        break;
      case (short) 1:
        data[KEY_BLOB_VERSION_DATA_OFFSET] = KMArray.cast(parsedKeyBlob).get((short) 0);
        data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 1);
        data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 2);
        data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 3);
        data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 4);
        data[PUB_KEY] = KMType.INVALID_VALUE;
        if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V1) {
          data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 5);
        }
        break;
      case (short) 2:
      case (short) 3:
        data[SECRET] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_SECRET);
        data[NONCE] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_NONCE);
        data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_AUTH_TAG);
        data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PARAMS);
        data[KEY_BLOB_VERSION_DATA_OFFSET] =
            KMArray.cast(parsedKeyBlob).get(KEY_BLOB_VERSION_OFFSET);
        data[CUSTOM_TAGS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_CUSTOM_TAGS);
        data[PUB_KEY] = KMType.INVALID_VALUE;
        if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V2_V3) {
          data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PUB_KEY);
        }
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
  }

  // Decode the KeyBlob from CBOR structures to the sub types of KMType.
  private void decodeKeyBlob(short version, short keyBlob) {
    // Decode KeyBlob and read the KeyBlob params based on the version.
    short parsedBlob =
        decoder.decodeArray(
            createKeyBlobExp(version),
            KMByteBlob.cast(keyBlob).getBuffer(),
            KMByteBlob.cast(keyBlob).getStartOff(),
            KMByteBlob.cast(keyBlob).length());
    short minArraySize = 0;
    switch (version) {
      case 0:
        minArraySize = SYM_KEY_BLOB_SIZE_V0;
        break;
      case 1:
        minArraySize = SYM_KEY_BLOB_SIZE_V1;
        break;
      case 2:
      case 3:
        minArraySize = SYM_KEY_BLOB_SIZE_V2_V3;
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    // KeyBlob size should not be less than the minimum KeyBlob size.
    if (KMArray.cast(parsedBlob).length() < minArraySize) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    readKeyBlobParams(version, parsedBlob);
  }

  // Decrypts the secret key in the KeyBlob. The secret can be a Symmetric or Asymmetric key.
  private void processDecryptSecret(short version, short appId, short appData, byte[] scratchPad) {
    data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced();
    data[SB_PARAMETERS] =
        KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced();
    data[SW_PARAMETERS] =
        KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced();
    data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]);

    data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, data[ROT], scratchPad);
    // Decrypt Secret and verify auth tag
    decryptSecret(scratchPad, version);
    short keyBlobSecretOff = 0;
    switch (version) {
      case 0:
        // V0 KeyBlob
        // KEY_BLOB = [
        //     SECRET,
        //     NONCE,
        //     AUTH_TAG,
        //     KEY_CHARACTERISTICS,
        //     PUBKEY
        // ]
        keyBlobSecretOff = (short) 0;
        break;
      case 1:
        // V1 KeyBlob
        // KEY_BLOB = [
        //     VERSION,
        //     SECRET,
        //     NONCE,
        //     AUTH_TAG,
        //     KEY_CHARACTERISTICS,
        //     PUBKEY
        // ]
        keyBlobSecretOff = (short) 1;
        break;
      case 2:
      case 3:
        // V2 KeyBlob
        // KEY_BLOB = [
        //     VERSION,
        //     SECRET,
        //     NONCE,
        //     AUTH_TAG,
        //     KEY_CHARACTERISTICS,
        //     CUSTOM_TAGS,
        //     PUBKEY
        // ]
        keyBlobSecretOff = KEY_BLOB_SECRET;
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    KMArray.cast(data[KEY_BLOB]).add(keyBlobSecretOff, data[SECRET]);
  }

  private void parseEncryptedKeyBlob(
      short keyBlob, short appId, short appData, byte[] scratchPad, short version) {
    // make root of trust blob
    data[ROT] = readROT(scratchPad, version);
    if (data[ROT] == KMType.INVALID_VALUE) {
      KMException.throwIt(KMError.UNKNOWN_ERROR);
    }
    try {
      decodeKeyBlob(version, keyBlob);
      processDecryptSecret(version, appId, appData, scratchPad);
    } catch (Exception e) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
  }

  private void decryptSecret(byte[] scratchPad, short version) {
    // derive master key - stored in derivedKey
    short len;
    short authDataOff = 0;
    short authDataLen = 0;
    byte[] authDataBuff = null;
    switch (version) {
      case 3:
        len = deriveKey(scratchPad);
        break;

      case 2:
      case 1:
      case 0:
        makeAuthData(version, scratchPad);
        len = deriveKeyForOldKeyBlobs(scratchPad);
        authDataBuff = repository.getHeap();
        authDataOff = data[AUTH_DATA];
        authDataLen = data[AUTH_DATA_LENGTH];
        break;
      default:
        KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    if (!seProvider.aesGCMDecrypt(
        KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(),
        KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(),
        KMByteBlob.cast(data[DERIVED_KEY]).length(),
        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(),
        authDataBuff,
        authDataOff,
        authDataLen,
        KMByteBlob.cast(data[AUTH_TAG]).getBuffer(),
        KMByteBlob.cast(data[AUTH_TAG]).getStartOff(),
        KMByteBlob.cast(data[AUTH_TAG]).length())) {
      KMException.throwIt(KMError.INVALID_KEY_BLOB);
    }
    // Copy the decrypted secret
    data[SECRET] =
        KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length());
  }

  private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) {
    Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0);
    Util.arrayCopyNonAtomic(
        KMInteger.cast(authTime).getBuffer(),
        KMInteger.cast(authTime).getStartOff(),
        scratchPad,
        (short) (8 - KMInteger.cast(timeStamp).length()),
        KMInteger.cast(timeStamp).length());

    // Copy timestamp to scratchpad
    Util.arrayCopyNonAtomic(
        KMInteger.cast(timeStamp).getBuffer(),
        KMInteger.cast(timeStamp).getStartOff(),
        scratchPad,
        (short) (16 - KMInteger.cast(timeStamp).length()),
        KMInteger.cast(timeStamp).length());

    // add authTime in millis to timestamp.
    KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16);
    return KMInteger.uint_64(scratchPad, (short) 16);
  }

  public void powerReset() {
    // TODO handle power reset signal.
    releaseAllOperations();
    resetWrappingKey();
  }

  private void updateTrustedConfirmationOperation(KMOperationState op) {
    if (op.isTrustedConfirmationRequired()) {
      op.getTrustedConfirmationSigner()
          .update(
              KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
              KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
              KMByteBlob.cast(data[INPUT_DATA]).length());
    }
  }

  private void finishTrustedConfirmationOperation(KMOperationState op) {
    // Perform trusted confirmation if required
    if (op.isTrustedConfirmationRequired()) {
      if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) {
        KMException.throwIt(KMError.NO_USER_CONFIRMATION);
      }

      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(data[CONFIRMATION_TOKEN]).getBuffer(),
                  KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(),
                  KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length());
      if (!verified) {
        KMException.throwIt(KMError.NO_USER_CONFIRMATION);
      }
    }
  }

  private boolean isDigestSupported(short alg, short digest) {
    switch (alg) {
      case KMType.RSA:
      case KMType.EC:
        if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) {
          return false;
        }
        break;
      case KMType.HMAC:
        if (digest != KMType.SHA2_256) {
          return false;
        }
        break;
      default:
        break;
    }
    return true;
  }

  /*
   * Returns the current package version.
   */
  protected abstract short getPackageVersion();

  /*
   * Returns the expected P1 and P2 parameters as short.
   */
  protected abstract short getP1P2();

  /*
   * Returns the current KeyMint HAL Version.
   */
  protected abstract short halVersion();

  /*
   * Returns the current KeyMint Attestation Version.
   */
  protected abstract short attestVersion();
}
