diff options
Diffstat (limited to 'ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java')
-rw-r--r-- | ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java | 5172 |
1 files changed, 5172 insertions, 0 deletions
diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java new file mode 100644 index 0000000..d8bc21d --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -0,0 +1,5172 @@ +/* + * 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 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; + // This is the P1P2 constant of the APDU command header. + protected static final short KM_HAL_VERSION = (short) 0x6000; + // 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_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 + private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 48; // 0x50 + 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 validateCertChain( + boolean validateEekRoot, + byte expCertAlg, + byte expLeafCertAlg, + short certChainArr, + byte[] scratchPad, + Object[] authorizedEekRoots) { + short len = KMArray.cast(certChainArr).length(); + short coseHeadersExp = KMCoseHeaders.exp(); + // prepare exp for coseky + short coseKeyExp = KMCoseKey.exp(); + short ptr1; + short ptr2; + short signStructure; + short encodedLen; + short prevCoseKey = 0; + short keySize; + short alg = expCertAlg; + short index; + for (index = 0; index < len; index++) { + ptr1 = KMArray.cast(certChainArr).get(index); + + // validate protected Headers + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if (!KMCoseHeaders.cast(ptr2).isDataValid(rkp.rkpTmpVariables, alg, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // parse and get the public key from payload. + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if ((index == (short) (len - 1)) && len > 1) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2) + .isDataValid( + rkp.rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + alg, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 + == Util.arrayCompare( + scratchPad, + (short) 0, + (byte[]) authorizedEekRoots[i], + (short) 0, + (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short signatureLen = + rkp.encodeES256CoseSignSignature( + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getStartOff(), + KMByteBlob.length(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + scratchPad, + (short) (keySize + encodedLen)); + + if (!seProvider.ecVerify256( + scratchPad, + (short) 0, + keySize, + scratchPad, + keySize, + encodedLen, + scratchPad, + (short) (keySize + encodedLen), + signatureLen)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } + + 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, + false); + 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. + private 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_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 + final byte version = 3; + // 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_8(version)); + 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 aesGCMEncrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + short len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + 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()); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short aesGCMDecrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + 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(), + scratchPad, + (short) 0, + 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 KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + } + + 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], + scratchPad); + 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, 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 = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.cast(notBefore).getValue(); + short notAfter = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.cast(notAfter).getValue(); + // 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 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); + // 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.VERIFICATION_FAILED); + } + tmpVariables[0] = op.getAuthTime(); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required + 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); + } + if (!authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad)) { + 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. + if (kmDataStore.getDeviceLockPasswordOnly()) { + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + 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.INVALID_MAC_LENGTH); + } + if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + 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; + } + + private boolean authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + return false; + } + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { + return false; + } + // check auth type + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) authType) == 0) { + return false; + } + return true; + } + + 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 = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { + // authenticate user + if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { + 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((byte) authType); + // 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(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + 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(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + 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; + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + 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 initSystemBootParams( + short osVersion, short osPatchLevel, short vendorPatchLevel, short bootPatchLevel) { + osVersion = KMInteger.uint_16(osVersion); + osPatchLevel = KMInteger.uint_16(osPatchLevel); + vendorPatchLevel = KMInteger.uint_16((short) vendorPatchLevel); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + } + + 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); + } + + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + + 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; + } +} |