blob: e12228362ab839fbba9aa6231947f099716def53 [file] [log] [blame]
/*
* 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_SECOND_IMEI,
KMType.ATTESTATION_ID_MANUFACTURER,
KMType.ATTESTATION_ID_MEID,
KMType.ATTESTATION_ID_MODEL,
KMType.ATTESTATION_ID_PRODUCT,
KMType.ATTESTATION_ID_SERIAL
};
// Below are the constants of instructions in APDU command header.
// Top 32 commands are reserved for provisioning.
private static final byte KEYMINT_CMD_APDU_START = 0x20;
// RKP
public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; // 0x3B
public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; // 0x3C
public static final byte INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29; // 0x3D
public static final byte INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30; // 0x3E
// Constant
public static final byte INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31; // 0x3F
public static final byte INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32; // 0x40
public static final byte INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33; // 0x41
public static final byte INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34; // 0x42
public static final byte INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35; // 0x43
public static final byte INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36; // 0x44
private static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; // 0x21
private static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; // 0x22
private static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; // 0x23
private static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; // 0x24
private static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; // 0x25
private static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; // 0x26
private static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; // 0x27
private static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; // 0x28
private static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; // 0x29
private static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; // 0x2A
private static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; // 0x2B
private static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; // 0x2C
private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; // 0x2D
private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; // 0x2E
private static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; // 0x2F
private static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; // 0x30
private static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; // 0x31
private static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; // 0x32
private static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; // 0x33
private static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20; // 0x34
private static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; // 0x35
private static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; // 0x36
private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; // 0x37
private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; // 0x38
private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; // 0x39
private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; // 0x3A
// The instructions from 0x43 to 0x4C will be reserved for KeyMint 1.0 for any future use.
// KeyMint 2.0 Instructions
private static final byte INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45; // 0x4D
private static final byte INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46; // 0x4E
private static final byte INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47; // 0x4F
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 generateDiceCertChain(byte[] scratchPad) {
if (kmDataStore.isProvisionLocked()) {
KMException.throwIt(KMError.STATUS_FAILED);
}
KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair();
short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0);
short coseKey =
KMCose.constructCoseKey(
rkp.rkpTmpVariables,
KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2),
KMType.INVALID_VALUE,
KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
KMInteger.uint_8(KMCose.COSE_ECCURVE_256),
scratchPad,
(short) 0,
temp,
KMType.INVALID_VALUE);
temp =
KMKeymasterApplet.encodeToApduBuffer(
coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
// Construct payload.
short payload =
KMCose.constructCoseCertPayload(
KMCosePairTextStringTag.instance(
KMInteger.uint_8(KMCose.ISSUER),
KMTextString.instance(
KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)),
KMCosePairTextStringTag.instance(
KMInteger.uint_8(KMCose.SUBJECT),
KMTextString.instance(
KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)),
KMCosePairByteBlobTag.instance(
KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0),
KMByteBlob.instance(scratchPad, (short) 0, temp)),
KMCosePairByteBlobTag.instance(
KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0),
KMByteBlob.instance(
KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length)));
// temp temporarily holds the length of encoded cert payload.
temp =
KMKeymasterApplet.encodeToApduBuffer(
payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
payload = KMByteBlob.instance(scratchPad, (short) 0, temp);
// protected header
short protectedHeader =
KMCose.constructHeaders(
rkp.rkpTmpVariables,
KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
KMType.INVALID_VALUE,
KMType.INVALID_VALUE,
KMType.INVALID_VALUE);
// temp temporarily holds the length of encoded headers.
temp =
KMKeymasterApplet.encodeToApduBuffer(
protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp);
// unprotected headers.
short arr = KMArray.instance((short) 0);
short unprotectedHeader = KMCoseHeaders.instance(arr);
// construct cose sign structure.
short coseSignStructure =
KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload);
// temp temporarily holds the length of encoded sign structure.
// Encode cose Sign_Structure.
temp =
KMKeymasterApplet.encodeToApduBuffer(
coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
// do sign
short len =
seProvider.signWithDeviceUniqueKey(
deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp);
len =
KMAsn1Parser.instance()
.decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp);
coseSignStructure = KMByteBlob.instance(scratchPad, temp, len);
// construct cose_sign1
short coseSign1 =
KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure);
// [Cose_Key, Cose_Sign1]
short dcc = KMArray.instance((short) 2);
KMArray.cast(dcc).add((short) 0, coseKey);
KMArray.cast(dcc).add((short) 1, coseSign1);
return dcc;
}
protected void initHmacNonceAndSeed() {
short nonce = repository.alloc((short) 32);
seProvider.newRandomNumber(
repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE);
kmDataStore.initHmacNonce(repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE);
}
private void releaseAllOperations() {
short index = 0;
while (index < MAX_OPERATIONS_COUNT) {
opTable[index].reset();
index++;
}
}
private KMOperationState reserveOperation(short algorithm, short opHandle) {
short index = 0;
while (index < MAX_OPERATIONS_COUNT) {
if (opTable[index].getAlgorithm() == KMType.INVALID_VALUE) {
opTable[index].reset();
opTable[index].setAlgorithm(algorithm);
opTable[index].setHandle(
KMInteger.cast(opHandle).getBuffer(),
KMInteger.cast(opHandle).getStartOff(),
KMInteger.cast(opHandle).length());
return opTable[index];
}
index++;
}
return null;
}
private KMOperationState findOperation(short handle) {
return findOperation(
KMInteger.cast(handle).getBuffer(),
KMInteger.cast(handle).getStartOff(),
KMInteger.cast(handle).length());
}
private KMOperationState findOperation(byte[] opHandle, short start, short len) {
short index = 0;
while (index < MAX_OPERATIONS_COUNT) {
if (opTable[index].compare(opHandle, start, len) == 0) {
if (opTable[index].getAlgorithm() != KMType.INVALID_VALUE) {
return opTable[index];
}
}
index++;
}
return null;
}
private void releaseOperation(KMOperationState op) {
op.reset();
}
/**
* Selects this applet.
*
* @return Returns true if the keymaster is in correct state
*/
@Override
public boolean select() {
repository.onSelect();
return true;
}
/** De-selects this applet. */
@Override
public void deselect() {
repository.onDeselect();
}
/** Uninstalls the applet after cleaning the repository. */
@Override
public void uninstall() {
repository.onUninstall();
}
protected short mapISOErrorToKMError(short reason) {
switch (reason) {
case ISO7816.SW_CLA_NOT_SUPPORTED:
return KMError.UNSUPPORTED_CLA;
case ISO7816.SW_CONDITIONS_NOT_SATISFIED:
return KMError.SW_CONDITIONS_NOT_SATISFIED;
case ISO7816.SW_COMMAND_NOT_ALLOWED:
return KMError.CMD_NOT_ALLOWED;
case ISO7816.SW_DATA_INVALID:
return KMError.INVALID_DATA;
case ISO7816.SW_INCORRECT_P1P2:
return KMError.INVALID_P1P2;
case ISO7816.SW_INS_NOT_SUPPORTED:
return KMError.UNSUPPORTED_INSTRUCTION;
case ISO7816.SW_WRONG_LENGTH:
return KMError.SW_WRONG_LENGTH;
case ISO7816.SW_UNKNOWN:
default:
return KMError.UNKNOWN_ERROR;
}
}
protected short mapCryptoErrorToKMError(short reason) {
switch (reason) {
case CryptoException.ILLEGAL_USE:
return KMError.CRYPTO_ILLEGAL_USE;
case CryptoException.ILLEGAL_VALUE:
return KMError.CRYPTO_ILLEGAL_VALUE;
case CryptoException.INVALID_INIT:
return KMError.CRYPTO_INVALID_INIT;
case CryptoException.NO_SUCH_ALGORITHM:
return KMError.CRYPTO_NO_SUCH_ALGORITHM;
case CryptoException.UNINITIALIZED_KEY:
return KMError.CRYPTO_UNINITIALIZED_KEY;
default:
return KMError.UNKNOWN_ERROR;
}
}
public void updateApduStatusFlags(short apduIns) {
switch (apduIns) {
case INS_EXPORT_KEY_CMD:
case INS_DELETE_ALL_KEYS_CMD:
case INS_DESTROY_ATT_IDS_CMD:
case INS_VERIFY_AUTHORIZATION_CMD:
case INS_GET_HMAC_SHARING_PARAM_CMD:
case INS_GET_HW_INFO_CMD:
case INS_EARLY_BOOT_ENDED_CMD:
case INS_GET_ROT_CHALLENGE_CMD:
case INS_GET_ROT_DATA_CMD:
case INS_GET_RKP_HARDWARE_INFO:
case INS_FINISH_SEND_DATA_CMD:
case INS_GET_UDS_CERTS_CMD:
case INS_GET_DICE_CERT_CHAIN_CMD:
apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0;
break;
default:
// By default the instruction is set to case 4 command instruction.
break;
}
}
/**
* Processes an incoming APDU and handles it using command objects.
*
* @param apdu the incoming APDU
*/
@Override
public void process(APDU apdu) {
try {
resetTransientBuffers();
repository.onProcess();
// If this is select applet apdu which is selecting this applet then return
if (apdu.isISOInterindustryCLA()) {
if (selectingApplet()) {
return;
}
}
byte[] apduBuffer = apdu.getBuffer();
byte apduIns = apduBuffer[ISO7816.OFFSET_INS];
if (!isKeyMintReady(apduIns)) {
ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
}
switch (apduIns) {
case INS_INIT_STRONGBOX_CMD:
processInitStrongBoxCmd(apdu);
sendResponse(apdu, KMError.OK);
return;
case INS_GENERATE_KEY_CMD:
processGenerateKey(apdu);
break;
case INS_IMPORT_KEY_CMD:
processImportKeyCmd(apdu);
break;
case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD:
processBeginImportWrappedKeyCmd(apdu);
break;
case INS_FINISH_IMPORT_WRAPPED_KEY_CMD:
processFinishImportWrappedKeyCmd(apdu);
break;
case INS_EXPORT_KEY_CMD:
processExportKeyCmd(apdu);
break;
case INS_UPGRADE_KEY_CMD:
processUpgradeKeyCmd(apdu);
break;
case INS_DELETE_KEY_CMD:
processDeleteKeyCmd(apdu);
break;
case INS_DELETE_ALL_KEYS_CMD:
processDeleteAllKeysCmd(apdu);
break;
case INS_ADD_RNG_ENTROPY_CMD:
processAddRngEntropyCmd(apdu);
break;
case INS_COMPUTE_SHARED_HMAC_CMD:
processComputeSharedHmacCmd(apdu);
break;
case INS_DESTROY_ATT_IDS_CMD:
processDestroyAttIdsCmd(apdu);
break;
case INS_VERIFY_AUTHORIZATION_CMD:
processVerifyAuthorizationCmd(apdu);
break;
case INS_GET_HMAC_SHARING_PARAM_CMD:
processGetHmacSharingParamCmd(apdu);
break;
case INS_GET_KEY_CHARACTERISTICS_CMD:
processGetKeyCharacteristicsCmd(apdu);
break;
case INS_GET_HW_INFO_CMD:
processGetHwInfoCmd(apdu);
break;
case INS_BEGIN_OPERATION_CMD:
processBeginOperationCmd(apdu);
break;
case INS_UPDATE_OPERATION_CMD:
processUpdateOperationCmd(apdu);
break;
case INS_FINISH_OPERATION_CMD:
processFinishOperationCmd(apdu);
break;
case INS_ABORT_OPERATION_CMD:
processAbortOperationCmd(apdu);
break;
case INS_DEVICE_LOCKED_CMD:
processDeviceLockedCmd(apdu);
break;
case INS_EARLY_BOOT_ENDED_CMD:
processEarlyBootEndedCmd(apdu);
break;
case INS_UPDATE_AAD_OPERATION_CMD:
processUpdateAadOperationCmd(apdu);
break;
case INS_GENERATE_RKP_KEY_CMD:
case INS_BEGIN_SEND_DATA_CMD:
case INS_UPDATE_KEY_CMD:
case INS_FINISH_SEND_DATA_CMD:
case INS_GET_RKP_HARDWARE_INFO:
case INS_GET_UDS_CERTS_CMD:
case INS_GET_DICE_CERT_CHAIN_CMD:
rkp.process(apduIns, apdu);
break;
// KeyMint 2.0
case INS_GET_ROT_CHALLENGE_CMD:
processGetRootOfTrustChallenge(apdu);
break;
case INS_GET_ROT_DATA_CMD:
sendResponse(apdu, KMError.UNIMPLEMENTED);
break;
case INS_SEND_ROT_DATA_CMD:
processSendRootOfTrust(apdu);
break;
default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
}
} catch (KMException exception) {
freeOperations();
resetWrappingKey();
sendResponse(apdu, KMException.reason());
} catch (ISOException exp) {
freeOperations();
resetWrappingKey();
sendResponse(apdu, mapISOErrorToKMError(exp.getReason()));
} catch (CryptoException e) {
freeOperations();
resetWrappingKey();
sendResponse(apdu, mapCryptoErrorToKMError(e.getReason()));
} catch (Exception e) {
freeOperations();
resetWrappingKey();
sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR);
} finally {
repository.clean();
}
}
private void processGetRootOfTrustChallenge(APDU apdu) {
byte[] scratchpad = apdu.getBuffer();
// Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of
// trust data.
seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16);
kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16);
short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16);
short arr = KMArray.instance((short) 2);
KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
KMArray.cast(arr).add((short) 1, challenge);
sendOutgoing(apdu, arr);
}
private short sendRootOfTrustCmd(APDU apdu) {
short arrInst = KMArray.instance((short) 4);
short headers = KMCoseHeaders.exp();
KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp());
KMArray.cast(arrInst).add((short) 1, headers);
KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp());
KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp());
short semanticTag = KMSemanticTag.exp(arrInst);
short arr = KMArray.exp(semanticTag);
return receiveIncoming(apdu, arr);
}
private void processSendRootOfTrust(APDU apdu) {
byte[] scratchPad = apdu.getBuffer();
short cmd = KMType.INVALID_VALUE;
// As per VTS if the input data is empty or not well-formed
// CoseMac return VERIFICATION_FAILED error.
try {
cmd = sendRootOfTrustCmd(apdu);
} catch (Exception e) {
KMException.throwIt(KMError.VERIFICATION_FAILED);
}
short semanticTag = KMArray.cast(cmd).get((short) 0);
short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr();
// Exp for KMCoseHeaders
short coseHeadersExp = KMCoseHeaders.exp();
// validate protected Headers
short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET);
ptr =
decoder.decode(
coseHeadersExp,
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
if (!KMCoseHeaders.cast(ptr)
.isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) {
KMException.throwIt(KMError.VERIFICATION_FAILED);
}
// Validate the Mac
short len = kmDataStore.getChallenge(scratchPad, (short) 0);
short extAad = KMByteBlob.instance(scratchPad, (short) 0, len);
// Compute CoseMac Structure and compare the macs.
short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET);
short macStructure =
KMCose.constructCoseMacStructure(
KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET),
extAad,
rotPayload);
short encodedLen =
KMKeymasterApplet.encodeToApduBuffer(
macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
if (!seProvider.hmacVerify(
kmDataStore.getComputedHmacKey(),
scratchPad,
(short) 0,
encodedLen,
KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(),
KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(),
KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) {
KMException.throwIt(KMError.VERIFICATION_FAILED);
}
// Store the data only once after reboot.
// Allow set boot params only when the host device reboots and the applet is in
// active state. If host does not support boot signal event, then allow this
// instruction any time.
kmDataStore.getDeviceBootStatus(scratchPad, (short) 0);
if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) {
// store the data.
storeRootOfTrust(rotPayload, scratchPad);
kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS);
}
// Invalidate the challenge
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0);
kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16);
sendResponse(apdu, KMError.OK);
}
private void storeRootOfTrust(short rotPayload, byte[] scratchPad) {
short byteBlobExp = KMByteBlob.exp();
short intExp = KMInteger.exp();
short boolExp = KMSimpleValue.exp();
short arr = KMArray.instance((short) 5);
KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key.
KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked.
KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State.
KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash.
KMArray.cast(arr).add((short) 4, intExp); // Boot patch level
short semanticExp = KMSemanticTag.exp(arr);
short semanticPtr =
decoder.decode(
semanticExp,
KMByteBlob.cast(rotPayload).getBuffer(),
KMByteBlob.cast(rotPayload).getStartOff(),
KMByteBlob.cast(rotPayload).length());
short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr();
// Store verified boot key
short ptr = KMArray.cast(rotArr).get((short) 0);
kmDataStore.setBootKey(
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
// Store Boot device locked.
ptr = KMArray.cast(rotArr).get((short) 1);
kmDataStore.setDeviceLocked(
(KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false);
// Store verified boot state
ptr = KMArray.cast(rotArr).get((short) 2);
kmDataStore.setBootState(KMInteger.cast(ptr).getShort());
// Store Verified boot hash
ptr = KMArray.cast(rotArr).get((short) 3);
kmDataStore.setVerifiedBootHash(
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
// Store boot patch level
ptr = KMArray.cast(rotArr).get((short) 4);
kmDataStore.setBootPatchLevel(
KMInteger.cast(ptr).getBuffer(),
KMInteger.cast(ptr).getStartOff(),
KMInteger.cast(ptr).length());
}
// After every device boot, the Keymaster becomes ready to execute all the commands only after
// 1. boot parameters are set,
// 2. system properties are set and
// 3. computed the shared secret successfully.
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_DELETE_ALL_KEYS_CMD:
case INS_INIT_STRONGBOX_CMD:
case INS_GET_ROT_CHALLENGE_CMD:
case INS_SEND_ROT_DATA_CMD:
return true;
default:
break;
}
return false;
}
private void generateUniqueOperationHandle(byte[] buf, short offset, short len) {
do {
seProvider.newRandomNumber(buf, offset, len);
} while (null != findOperation(buf, offset, len));
}
private void freeOperations() {
if (data[OP_HANDLE] != KMType.INVALID_VALUE) {
KMOperationState op = findOperation(data[OP_HANDLE]);
if (op != null) {
releaseOperation(op);
}
}
}
private void processEarlyBootEndedCmd(APDU apdu) {
kmDataStore.setEarlyBootEndedStatus(true);
sendResponse(apdu, KMError.OK);
}
private short deviceLockedCmd(APDU apdu) {
short cmd = KMArray.instance((short) 2);
short ptr = KMVerificationToken.exp();
// passwordOnly
KMArray.cast(cmd).add((short) 0, KMInteger.exp());
// verification token
KMArray.cast(cmd).add((short) 1, ptr);
return receiveIncoming(apdu, cmd);
}
private void processDeviceLockedCmd(APDU apdu) {
short cmd = deviceLockedCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
short passwordOnly = KMArray.cast(cmd).get((short) 0);
short verToken = KMArray.cast(cmd).get((short) 1);
passwordOnly = KMInteger.cast(passwordOnly).getByte();
validateVerificationToken(verToken, scratchPad);
short verTime = KMVerificationToken.cast(verToken).getTimestamp();
short lastDeviceLockedTime;
try {
lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp();
} catch (KMException e) {
lastDeviceLockedTime = KMInteger.uint_8((byte) 0);
}
if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) {
Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0);
KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64);
kmDataStore.setDeviceLock(true);
kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01);
kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64);
}
sendResponse(apdu, KMError.OK);
}
private void resetWrappingKey() {
if (!isValidWrappingKey()) {
return;
}
Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0);
wrappingKey[0] = -1;
}
private boolean isValidWrappingKey() {
return wrappingKey[0] != -1;
}
private short getWrappingKey() {
return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE);
}
private void setWrappingKey(short key) {
if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
wrappingKey[0] = 0;
Util.arrayCopyNonAtomic(
KMByteBlob.cast(key).getBuffer(),
KMByteBlob.cast(key).getStartOff(),
wrappingKey,
(short) 1,
WRAPPING_KEY_SIZE);
}
protected void resetTransientBuffers() {
short index = 0;
while (index < data.length) {
data[index] = KMType.INVALID_VALUE;
index++;
}
index = 0;
while (index < tmpVariables.length) {
tmpVariables[index] = KMType.INVALID_VALUE;
index++;
}
}
public void sendOutgoing(
APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) {
// This is the special case where the output is encoded manually without using
// the encoder algorithm. Encoder creates a duplicate copy for each KMType Object.
// The output of the generateKey, importKey and importWrappedKey commands are huge so
// by manually encoding we can avoid duplicate copies.
// The output data is directly written to the end of heap in the below order
// output = [
// errorCode : uint // ErrorCode
// keyBlob : bstr // KeyBlob.
// keyChars
// certifcate
// ]
// certificate = [
// x509_cert : bstr // X509 certificate
// ]
// keyChars = { // Map
// }
byte[] buffer = repository.getHeap();
if (cert == null) {
// This happens for Symmetric keys.
short bufferStart = repository.allocReclaimableMemory((short) 1);
buffer[bufferStart] = (byte) 0x80; // Array of 0 length.
} else {
// Encode the certificate into cbor data at the end of the heap
// certData = [
// x509_cert : bstr // X509 certificate
// ]
short bufferStart =
encoder.encodeCert(
repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength());
// reclaim the unused memory in the certificate.
repository.reclaimMemory((short) (bufferStart - certStart));
}
// Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE]
encodeKeyCharacteristics(keyChars);
// and encode it to the end of the buffer before KEY_CHARACTERISTICS
encodeKeyBlob(keyblob);
// Write Array header and ErrorCode before data[KEY_BLOB]
short bufferStartOffset = repository.allocReclaimableMemory((short) 2);
Util.setShort(buffer, bufferStartOffset, (short) 0x8400);
short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset);
/* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must
* be invoked prior to calling setOutgoing(). Otherwise, erroneous
* behavior may result
* */
if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1
&& apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0
&& APDU.getProtocol() == APDU.PROTOCOL_T0) {
apdu.setIncomingAndReceive();
}
// Send data
apdu.setOutgoing();
apdu.setOutgoingLength(bufferLength);
apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength);
}
private void processGetHwInfoCmd(APDU apdu) {
// No arguments expected
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 aesGCMDecrypt(
short aesSecret, short input, short nonce, short authData, short authTag) {
short outPtr = KMByteBlob.instance(KMByteBlob.cast(input).length());
if (!seProvider.aesGCMDecrypt(
KMByteBlob.cast(aesSecret).getBuffer(),
KMByteBlob.cast(aesSecret).getStartOff(),
KMByteBlob.cast(aesSecret).length(),
KMByteBlob.cast(input).getBuffer(),
KMByteBlob.cast(input).getStartOff(),
KMByteBlob.cast(input).length(),
KMByteBlob.cast(outPtr).getBuffer(),
KMByteBlob.cast(outPtr).getStartOff(),
KMByteBlob.cast(nonce).getBuffer(),
KMByteBlob.cast(nonce).getStartOff(),
KMByteBlob.cast(nonce).length(),
KMByteBlob.cast(authData).getBuffer(),
KMByteBlob.cast(authData).getStartOff(),
KMByteBlob.cast(authData).length(),
KMByteBlob.cast(authTag).getBuffer(),
KMByteBlob.cast(authTag).getStartOff(),
KMByteBlob.cast(authTag).length())) {
KMException.throwIt(KMError.VERIFICATION_FAILED);
}
return outPtr;
}
private short finishImportWrappedKeyCmd(APDU apdu) {
short cmd = KMArray.instance((short) 8);
short params = KMKeyParameters.expAny();
KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key
KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format
KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob
KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag
KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce
KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA
KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid
KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid
return receiveIncoming(apdu, cmd);
}
// TODO remove cmd later on
private void processFinishImportWrappedKeyCmd(APDU apdu) {
short cmd = finishImportWrappedKeyCmd(apdu);
short keyParameters = KMArray.cast(cmd).get((short) 0);
short keyFmt = KMArray.cast(cmd).get((short) 1);
keyFmt = KMEnum.cast(keyFmt).getVal();
validateImportKey(keyParameters, keyFmt);
byte[] scratchPad = apdu.getBuffer();
// Step 4 - AES-GCM decrypt the wrapped key
data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2);
data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3);
data[NONCE] = KMArray.cast(cmd).get((short) 4);
data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5);
if (!isValidWrappingKey()) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
data[IMPORTED_KEY_BLOB] =
aesGCMDecrypt(
getWrappingKey(),
data[INPUT_DATA],
data[NONCE],
data[AUTH_DATA],
data[AUTH_TAG]);
resetWrappingKey();
// Step 5 - Import decrypted key
data[ORIGIN] = KMType.SECURELY_IMPORTED;
data[KEY_PARAMETERS] = keyParameters;
// create key blob array
importKey(apdu, keyFmt, scratchPad);
}
private KMAttestationCert makeCommonCert(byte[] scratchPad) {
short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]);
boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA;
KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider);
short subject =
KMKeyParameters.findTag(
KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]);
// If no subject name is specified then use the default subject name.
if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) {
subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length);
} else {
subject = KMByteTag.cast(subject).getValue();
}
cert.subjectName(subject);
// Validity period must be specified
short notBefore = getCertificateValidityDate(KMType.CERTIFICATE_NOT_BEFORE, scratchPad);
short notAfter = getCertificateValidityDate(KMType.CERTIFICATE_NOT_AFTER, scratchPad);
// VTS sends notBefore == Epoch.
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0);
short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8);
short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length);
if (KMInteger.compare(notBefore, epoch) == 0) {
cert.notBefore(
KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad);
} else {
cert.notBefore(notBefore, false, scratchPad);
}
// VTS sends notAfter == Dec 31st 9999
if (KMInteger.compare(notAfter, end) == 0) {
cert.notAfter(
KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad);
} else {
cert.notAfter(notAfter, false, scratchPad);
}
// Serial number
short serialNum =
KMKeyParameters.findTag(
KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]);
if (serialNum != KMType.INVALID_VALUE) {
serialNum = KMBignumTag.cast(serialNum).getValue();
} else {
serialNum = KMByteBlob.instance((short) 1);
KMByteBlob.cast(serialNum).add((short) 0, (byte) 1);
}
cert.serialNumber(serialNum);
return cert;
}
private short getCertificateValidityDate(short tag, byte[] scratchpad) {
short error = KMError.UNKNOWN_ERROR;
switch(tag) {
case KMType.CERTIFICATE_NOT_AFTER:
error = KMError.MISSING_NOT_AFTER;
Util.arrayCopyNonAtomic(dec319999Ms, (short) 0, scratchpad, (short) 0, (short) dec319999Ms.length);
break;
case KMType.CERTIFICATE_NOT_BEFORE:
error = KMError.MISSING_NOT_BEFORE;
Util.arrayFillNonAtomic(scratchpad, (short) 0, (short) 8, (byte) 0);
break;
default:
KMException.throwIt(KMError.INVALID_TAG);
}
short datePtr = KMKeyParameters.findTag(KMType.DATE_TAG, tag, data[KEY_PARAMETERS]);
if (datePtr == KMType.INVALID_VALUE ) {
if (data[ORIGIN] == KMType.SECURELY_IMPORTED) {
return KMInteger.instance(scratchpad, (short) 0, (short) 8);
}
KMException.throwIt(error);
}
return KMIntegerTag.cast(datePtr).getValue();
}
private KMAttestationCert makeAttestationCert(
short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) {
KMAttestationCert cert = makeCommonCert(scratchPad);
// Read App Id and App Data.
short appId = getApplicationId(attKeyParam);
short appData = getApplicationData(attKeyParam);
// Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR
// and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function.
short origBlob = data[KEY_BLOB];
short pubKey = data[PUB_KEY];
short privKey = data[SECRET];
short hwParams = data[HW_PARAMETERS];
short keyChars = data[KEY_CHARACTERISTICS];
short customTags = data[CUSTOM_TAGS];
// Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside
// isKeyUpgradeRequired function itself.
if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) {
KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
}
// Get the private key of the attest key.
short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET);
// Get the KeyCharacteristics and SB param of the attest key
short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS);
short attestKeySbParams =
KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced();
// If the attest key's purpose is not "attest key" then error.
short attKeyPurpose =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams);
if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) {
KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
}
KMAsn1Parser asn1Decoder = KMAsn1Parser.instance();
try {
asn1Decoder.validateDerSubject(issuer);
} catch (KMException e) {
KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME);
}
if (KMByteBlob.cast(issuer).length() > KMConfigurations.MAX_SUBJECT_DER_LEN) {
KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME);
}
// If issuer is not present then it is an error
if (KMByteBlob.cast(issuer).length() <= 0) {
KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME);
}
short alg = KMEnumTag.getValue(KMType.ALGORITHM, attestKeySbParams);
if (alg == KMType.RSA) {
short attestationKeyPublic = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY);
cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT);
} else if (alg == KMType.EC) {
cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT);
} else {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
cert.attestationChallenge(attChallenge);
cert.issuer(issuer);
// Restore back the global variables.
data[PUB_KEY] = pubKey;
data[SECRET] = privKey;
data[KEY_BLOB] = origBlob;
data[HW_PARAMETERS] = hwParams;
data[KEY_CHARACTERISTICS] = keyChars;
data[CUSTOM_TAGS] = customTags;
data[SW_PARAMETERS] =
KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced();
data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced();
data[SB_PARAMETERS] =
KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced();
cert.publicKey(data[PUB_KEY]);
// Save attestation application id - must be present.
short attAppId =
KMKeyParameters.findTag(
KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]);
if (attAppId == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING);
}
cert.extensionTag(attAppId, false);
// unique id byte blob - uses application id and temporal month count of
// creation time.
attAppId = KMByteTag.cast(attAppId).getValue();
setUniqueId(cert, attAppId, scratchPad);
// Add Attestation Ids if present
addAttestationIds(cert, scratchPad);
// Add Tags
addTags(data[HW_PARAMETERS], true, cert);
addTags(data[SW_PARAMETERS], false, cert);
// Add Device Boot locked status
cert.deviceLocked(kmDataStore.isDeviceBootLocked());
// VB data
cert.verifiedBootHash(getVerifiedBootHash(scratchPad));
cert.verifiedBootKey(getBootKey(scratchPad));
cert.verifiedBootState((byte) kmDataStore.getBootState());
return cert;
}
private KMAttestationCert makeSelfSignedCert(
short attPrivKey, short attPubKey, short mode, byte[] scratchPad) {
KMAttestationCert cert = makeCommonCert(scratchPad);
short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
short subject =
KMKeyParameters.findTag(
KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]);
// If no subject name is specified then use the default subject name.
if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) {
subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length);
} else {
subject = KMByteTag.cast(subject).getValue();
}
if (alg == KMType.RSA) {
cert.rsaAttestKey(attPrivKey, attPubKey, (byte) mode);
} else {
cert.ecAttestKey(attPrivKey, (byte) mode);
}
cert.issuer(subject);
cert.subjectName(subject);
cert.publicKey(attPubKey);
return cert;
}
protected short getBootKey(byte[] scratchPad) {
Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE, (byte) 0);
short len = kmDataStore.getBootKey(scratchPad, (short) 0);
if (len != VERIFIED_BOOT_KEY_SIZE) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE);
}
protected short getVerifiedBootHash(byte[] scratchPad) {
Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE, (byte) 0);
short len = kmDataStore.getVerifiedBootHash(scratchPad, (short) 0);
if (len != VERIFIED_BOOT_HASH_SIZE) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE);
}
// --------------------------------
// Only add the Attestation ids which are requested in the attestation parameters.
// If the requested attestation ids are not provisioned or deleted then
// throw CANNOT_ATTEST_IDS error. If there is mismatch in the attestation
// id values of both the requested parameters and the provisioned parameters
// then throw INVALID_TAG error.
private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) {
byte index = 0;
short attIdTag;
short attIdTagValue;
short storedAttIdLen;
while (index < (short) attTags.length) {
attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]);
if (attIdTag != KMType.INVALID_VALUE) {
attIdTagValue = KMByteTag.cast(attIdTag).getValue();
storedAttIdLen = kmDataStore.getAttestationId(attTags[index], scratchPad, (short) 0);
// Return CANNOT_ATTEST_IDS if Attestation IDs are not provisioned or
// Attestation IDs are deleted.
if (storedAttIdLen == 0) {
// Ignore the SECOND_IMEI tag if the previous Applet's KeyMint version is less than
// 3.0 and no SECOND_IMEI is provisioned.
if (!(kmDataStore.ignoreSecondImei
&& attTags[index] == KMType.ATTESTATION_ID_SECOND_IMEI)) {
KMException.throwIt(KMError.CANNOT_ATTEST_IDS);
}
} else {
// Return INVALID_TAG if Attestation IDs does not match.
if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length())
|| (0
!= Util.arrayCompare(
scratchPad,
(short) 0,
KMByteBlob.cast(attIdTagValue).getBuffer(),
KMByteBlob.cast(attIdTagValue).getStartOff(),
storedAttIdLen))) {
KMException.throwIt(KMError.CANNOT_ATTEST_IDS);
}
short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen);
cert.extensionTag(KMByteTag.instance(attTags[index], blob), true);
}
}
index++;
}
}
private void processDestroyAttIdsCmd(APDU apdu) {
kmDataStore.deleteAttestationIds();
sendResponse(apdu, KMError.OK);
}
private void processVerifyAuthorizationCmd(APDU apdu) {
sendResponse(apdu, KMError.UNIMPLEMENTED);
}
private short abortOperationCmd(APDU apdu) {
short cmd = KMArray.instance((short) 1);
KMArray.cast(cmd).add((short) 0, KMInteger.exp());
return receiveIncoming(apdu, cmd);
}
private void processAbortOperationCmd(APDU apdu) {
short cmd = abortOperationCmd(apdu);
data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
KMOperationState op = findOperation(data[OP_HANDLE]);
if (op == null) {
sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE);
} else {
releaseOperation(op);
sendResponse(apdu, KMError.OK);
}
}
private short finishOperationCmd(APDU apdu) {
short cmd = KMArray.instance((short) 6);
KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle
KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data
KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature
short authToken = KMHardwareAuthToken.exp();
KMArray.cast(cmd).add((short) 3, authToken); // auth token
short verToken = KMVerificationToken.exp();
KMArray.cast(cmd).add((short) 4, verToken); // time stamp token
KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token
return receiveIncoming(apdu, cmd);
}
private void processFinishOperationCmd(APDU apdu) {
short cmd = finishOperationCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
data[SIGNATURE] = KMArray.cast(cmd).get((short) 2);
data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3);
data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4);
data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5);
// Check Operation Handle
KMOperationState op = findOperation(data[OP_HANDLE]);
if (op == null) {
KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
}
// Authorize the finish operation
authorizeUpdateFinishOperation(op, scratchPad);
switch (op.getPurpose()) {
case KMType.SIGN:
finishTrustedConfirmationOperation(op);
case KMType.VERIFY:
finishSigningVerifyingOperation(op, scratchPad);
break;
case KMType.ENCRYPT:
finishEncryptOperation(op, scratchPad);
break;
case KMType.DECRYPT:
finishDecryptOperation(op, scratchPad);
break;
case KMType.AGREE_KEY:
finishKeyAgreementOperation(op, scratchPad);
break;
}
if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) {
data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
}
// Remove the operation handle
releaseOperation(op);
// make response
short resp = KMArray.instance((short) 2);
KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]);
sendOutgoing(apdu, resp);
}
private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) {
if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
finishAesDesOperation(op);
}
private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) {
short len = KMByteBlob.cast(data[INPUT_DATA]).length();
switch (op.getAlgorithm()) {
case KMType.RSA:
// Fill the scratch pad with zero
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
if (op.getPadding() == KMType.PADDING_NONE && len != 256) {
KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
}
len =
op.getOperation()
.finish(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
len,
scratchPad,
(short) 0);
data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len);
break;
case KMType.AES:
case KMType.DES:
finishAesDesOperation(op);
break;
}
}
private void finishAesDesOperation(KMOperationState op) {
short len = KMByteBlob.cast(data[INPUT_DATA]).length();
short blockSize = AES_BLOCK_SIZE;
if (op.getAlgorithm() == KMType.DES) {
blockSize = DES_BLOCK_SIZE;
}
if (op.getPurpose() == KMType.DECRYPT
&& len > 0
&& (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC)
&& ((short) (len % blockSize) != 0)) {
KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
}
if (op.getBlockMode() == KMType.GCM) {
if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) {
KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
}
if (op.isAesGcmUpdateAllowed()) {
op.setAesGcmUpdateComplete();
}
// Get the output size
len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8));
}
// If padding i.e. pkcs7 then add padding to right
// Output data can at most one block size more the input data in case of pkcs7 encryption
// In case of gcm we will allocate extra memory of the size equal to blocksize.
data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize));
try {
len =
op.getOperation()
.finish(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length(),
KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff());
} catch (CryptoException e) {
if (e.getReason() == CryptoException.ILLEGAL_USE) {
// As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length
// output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is
// provided via update() method. So ignore this exception in case if all below conditions
// are satisfied and simply return empty output.
// 1. padding mode is PADDING_NONE.
// 2. No input message is processed in update().
// 3. Zero length input data is passed in finish operation.
if ((op.getPadding() == KMType.PADDING_NONE)
&& !op.isInputMsgProcessed()
&& (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) {
len = 0;
} else {
KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
}
}
}
KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len);
}
private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) {
try {
KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]);
short len =
op.getOperation()
.finish(
KMByteBlob.cast(blob).getBuffer(),
KMByteBlob.cast(blob).getStartOff(),
KMByteBlob.cast(blob).length(),
scratchPad,
(short) 0);
data[OUTPUT_DATA] = KMByteBlob.instance((short) 32);
Util.arrayCopyNonAtomic(
scratchPad,
(short) 0,
KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(),
len);
} catch (CryptoException e) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
}
private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) {
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
switch (op.getAlgorithm()) {
case KMType.RSA:
// If there is no padding we can treat signing as a RSA decryption operation.
try {
if (op.getPurpose() == KMType.SIGN) {
// len of signature will be 256 bytes - but it can be less then 256 bytes
short len =
op.getOperation()
.sign(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length(),
scratchPad,
(short) 0);
// Maximum output size of signature is 256 bytes. - the signature will always be
// positive
data[OUTPUT_DATA] = KMByteBlob.instance((short) 256);
Util.arrayCopyNonAtomic(
scratchPad,
(short) 0,
KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
(short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len),
len);
} else {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
} catch (CryptoException e) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
break;
case KMType.EC:
short len = KMByteBlob.cast(data[INPUT_DATA]).length();
// If DIGEST NONE then truncate the input data to 32 bytes.
if (op.getDigest() == KMType.DIGEST_NONE && len > 32) {
len = 32;
}
if (op.getPurpose() == KMType.SIGN) {
// len of signature will be 512 bits i.e. 64 bytes
len =
op.getOperation()
.sign(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
len,
scratchPad,
(short) 0);
data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len);
} else {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
break;
case KMType.HMAC:
// As per Keymaster HAL documentation, the length of the Hmac output can
// be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no
// such provision to control the length of the Hmac output using JavaCard
// crypto APIs and the current implementation always returns 32 bytes
// length of Hmac output. So to provide support to TAG_MAC_LENGTH
// feature, we truncate the output signature to TAG_MAC_LENGTH and return
// the truncated signature back to the caller. At the time of verfication
// we again compute the signature of the plain text input, truncate it to
// TAG_MAC_LENGTH and compare it with the input signature for
// verification.
op.getOperation()
.sign(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length(),
scratchPad,
(short) 0);
if (op.getPurpose() == KMType.SIGN) {
// Copy only signature of mac length size.
data[OUTPUT_DATA] =
KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8));
} else if (op.getPurpose() == KMType.VERIFY) {
if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8))
|| KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) {
KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
}
if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) {
KMException.throwIt(KMError.INVALID_MAC_LENGTH);
}
if (0
!= Util.arrayCompare(
scratchPad,
(short) 0,
KMByteBlob.cast(data[SIGNATURE]).getBuffer(),
KMByteBlob.cast(data[SIGNATURE]).getStartOff(),
KMByteBlob.cast(data[SIGNATURE]).length())) {
KMException.throwIt(KMError.VERIFICATION_FAILED);
}
data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
}
break;
default: // This is should never happen
KMException.throwIt(KMError.OPERATION_CANCELLED);
break;
}
}
private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) {
// If one time user Authentication is required
if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) {
// Validate Verification Token.
validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad);
// validate operation handle.
short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge();
if (KMInteger.compare(ptr, op.getHandle()) != 0) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
tmpVariables[0] = op.getAuthTime();
tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp();
if (tmpVariables[2] == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
op.setAuthTimeoutValidated(true);
} else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required
if (!validateHwToken(data[HW_TOKEN], scratchPad)) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge();
if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
short len = op.getAuthType(scratchPad, (short) 0);
if (!authTokenMatches(op.getUserSecureId(), scratchPad, (short) 0, len, scratchPad, len)) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
}
}
private void authorizeKeyUsageForCount(byte[] scratchPad) {
short scratchPadOff = 0;
Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0);
short usageLimitBufLen =
KMIntegerTag.getValue(
scratchPad,
scratchPadOff,
KMType.UINT_TAG,
KMType.MAX_USES_PER_BOOT,
data[HW_PARAMETERS]);
if (usageLimitBufLen == KMType.INVALID_VALUE) {
return;
}
if (usageLimitBufLen > 4) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) {
// Get current counter, update and increment it.
short len =
kmDataStore.getRateLimitedKeyCount(
data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4));
if (len != 4) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
if (0
>= KMInteger.unsignedByteArrayCompare(
scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) {
KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED);
}
// Increment the counter.
Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0);
Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1);
KMUtils.add(
scratchPad,
scratchPadOff,
(short) (scratchPadOff + len),
(short) (scratchPadOff + len * 2));
kmDataStore.setRateLimitedKeyCount(
data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len);
} else {
// Persist auth tag.
if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) {
KMException.throwIt(KMError.TOO_MANY_OPERATIONS);
}
}
}
private void authorizeDeviceUnlock(byte[] scratchPad) {
// If device is locked and key characteristics requires unlocked device then check whether
// HW auth token has correct timestamp.
short ptr =
KMKeyParameters.findTag(
KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]);
if (ptr != KMType.INVALID_VALUE && kmDataStore.getDeviceLock()) {
if (data[HW_TOKEN] == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.DEVICE_LOCKED);
}
ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp();
// Check if the current auth time stamp is greater than device locked time stamp
short ts = kmDataStore.getDeviceTimeStamp();
if (KMInteger.compare(ptr, ts) <= 0) {
KMException.throwIt(KMError.DEVICE_LOCKED);
}
// Now check if the device unlock requires password only authentication and whether
// auth token is generated through password authentication or not.
scratchPad[0] = KMType.PASSWORD;
short authTypeLen = 1;
if (kmDataStore.getDeviceLockPasswordOnly()) {
if (!hwAuthTypeMatches(scratchPad, (short) 0, authTypeLen, scratchPad, authTypeLen)) {
KMException.throwIt(KMError.DEVICE_LOCKED);
}
}
// Unlock the device
// repository.deviceLockedFlag = false;
kmDataStore.setDeviceLock(false);
kmDataStore.clearDeviceLockTimeStamp();
}
}
private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) {
// concatenation length will be 37 + length of verified parameters list - which
// is typically empty
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
// Add "Auth Verification" - 17 bytes.
Util.arrayCopyNonAtomic(
authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length);
short len = (short) authVerification.length;
// concatenate challenge - 8 bytes
short ptr = KMVerificationToken.cast(verToken).getChallenge();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
// concatenate timestamp -8 bytes
ptr = KMVerificationToken.cast(verToken).getTimestamp();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
// concatenate security level - 4 bytes
scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT;
len += KMInteger.UINT_32;
// hmac the data
ptr = KMVerificationToken.cast(verToken).getMac();
return seProvider.hmacVerify(
kmDataStore.getComputedHmacKey(),
scratchPad,
(short) 0,
len,
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
}
private void validateVerificationToken(short verToken, byte[] scratchPad) {
short ptr = KMVerificationToken.cast(verToken).getMac();
// If mac length is zero then token is empty.
if (KMByteBlob.cast(ptr).length() == 0) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) {
// Throw Exception if none of the combination works.
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
}
private short updateOperationCmd(APDU apdu) {
short cmd = KMArray.instance((short) 4);
// Arguments
KMArray.cast(cmd).add((short) 0, KMInteger.exp());
KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
short authToken = KMHardwareAuthToken.exp();
KMArray.cast(cmd).add((short) 2, authToken);
short verToken = KMVerificationToken.exp();
KMArray.cast(cmd).add((short) 3, verToken);
return receiveIncoming(apdu, cmd);
}
private void processUpdateOperationCmd(APDU apdu) {
short cmd = updateOperationCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2);
data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3);
// Input data must be present even if it is zero length.
if (data[INPUT_DATA] == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
// Check Operation Handle and get op state
// Check Operation Handle
KMOperationState op = findOperation(data[OP_HANDLE]);
if (op == null) {
KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
}
// authorize the update operation
authorizeUpdateFinishOperation(op, scratchPad);
if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) {
// update the data.
op.getOperation()
.update(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length());
// update trusted confirmation operation
updateTrustedConfirmationOperation(op);
data[OUTPUT_DATA] = KMType.INVALID_VALUE;
} else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) {
// Update for encrypt/decrypt using RSA will not be supported because to do this op state
// will have to buffer the data - so reject the update if it is rsa algorithm.
if (op.getAlgorithm() == KMType.RSA) {
KMException.throwIt(KMError.OPERATION_CANCELLED);
}
short len = KMByteBlob.cast(data[INPUT_DATA]).length();
short blockSize = DES_BLOCK_SIZE;
if (op.getAlgorithm() == KMType.AES) {
blockSize = AES_BLOCK_SIZE;
if (op.getBlockMode() == KMType.GCM) {
// if input data present
if (len > 0) {
// no more future updateAAD allowed if input data present.
if (op.isAesGcmUpdateAllowed()) {
op.setAesGcmUpdateComplete();
}
}
}
}
// Allocate output buffer as input data is already block aligned
data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize));
// Otherwise just update the data.
// HAL consumes all the input and maintains a buffered data inside it. So the
// applet sends the inputConsumed length as same as the input length.
try {
len =
op.getOperation()
.update(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length(),
KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff());
} catch (CryptoException e) {
KMException.throwIt(KMError.INVALID_TAG);
}
if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) {
// This flag is used to denote that an input data of length > 0 is received and processed
// successfully in update command. This flag is later used in the finish operation
// to handle a particular use case, where a zero length input data on AES/DES algorithm
// with PADDING_NONE should return a zero length output with OK response.
op.setProcessedInputMsg(true);
}
// Adjust the Output data if it is not equal to input data.
// This happens in case of JCardSim provider.
KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len);
}
if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) {
data[OUTPUT_DATA] = KMByteBlob.instance((short) 0);
}
// Persist if there are any updates.
// op.persist();
// make response
short resp = KMArray.instance((short) 2);
KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]);
sendOutgoing(apdu, resp);
}
private short updateAadOperationCmd(APDU apdu) {
short cmd = KMArray.instance((short) 4);
KMArray.cast(cmd).add((short) 0, KMInteger.exp());
KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
short authToken = KMHardwareAuthToken.exp();
KMArray.cast(cmd).add((short) 2, authToken);
short verToken = KMVerificationToken.exp();
KMArray.cast(cmd).add((short) 3, verToken);
return receiveIncoming(apdu, cmd);
}
private void processUpdateAadOperationCmd(APDU apdu) {
short cmd = updateAadOperationCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0);
data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1);
data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2);
data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3);
// Input data must be present even if it is zero length.
if (data[INPUT_DATA] == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
// Check Operation Handle and get op state
// Check Operation Handle
KMOperationState op = findOperation(data[OP_HANDLE]);
if (op == null) {
KMException.throwIt(KMError.INVALID_OPERATION_HANDLE);
}
if (op.getAlgorithm() != KMType.AES) {
KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM);
}
if (op.getBlockMode() != KMType.GCM) {
KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE);
}
if (!op.isAesGcmUpdateAllowed()) {
KMException.throwIt(KMError.INVALID_TAG);
}
if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) {
KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
}
// authorize the update operation
authorizeUpdateFinishOperation(op, scratchPad);
try {
op.getOperation()
.updateAAD(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length());
} catch (CryptoException exp) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
// make response
short resp = KMArray.instance((short) 1);
KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
sendOutgoing(apdu, resp);
}
private short beginOperationCmd(APDU apdu) {
short cmd = KMArray.instance((short) 4);
// Arguments
short params = KMKeyParameters.expAny();
KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE));
KMArray.cast(cmd).add((short) 1, KMByteBlob.exp());
KMArray.cast(cmd).add((short) 2, params);
short authToken = KMHardwareAuthToken.exp();
KMArray.cast(cmd).add((short) 3, authToken);
return receiveIncoming(apdu, cmd);
}
private void processBeginOperationCmd(APDU apdu) {
// Receive the incoming request fully from the host into buffer.
short cmd = beginOperationCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
short purpose = KMArray.cast(cmd).get((short) 0);
data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1);
data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2);
data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3);
purpose = KMEnum.cast(purpose).getVal();
// Check for app id and app data.
data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]);
data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]);
// Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired
// function itself.
if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) {
KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE);
}
KMTag.assertPresence(
data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM);
short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]);
// If Blob usage tag is present in key characteristics then it should be standalone.
if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) {
if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) {
KMException.throwIt(KMError.UNSUPPORTED_TAG);
}
}
// Generate a random number for operation handle
short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE);
generateUniqueOperationHandle(
KMByteBlob.cast(buf).getBuffer(),
KMByteBlob.cast(buf).getStartOff(),
KMByteBlob.cast(buf).length());
/* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */
short opHandle =
KMInteger.instance(
KMByteBlob.cast(buf).getBuffer(),
KMByteBlob.cast(buf).getStartOff(),
KMByteBlob.cast(buf).length());
KMOperationState op = reserveOperation(algorithm, opHandle);
if (op == null) {
KMException.throwIt(KMError.TOO_MANY_OPERATIONS);
}
data[OP_HANDLE] = op.getHandle();
op.setPurpose((byte) purpose);
op.setKeySize(KMByteBlob.cast(data[SECRET]).length());
authorizeAndBeginOperation(op, scratchPad);
switch (op.getPurpose()) {
case KMType.SIGN:
beginTrustedConfirmationOperation(op);
case KMType.VERIFY:
beginSignVerifyOperation(op);
break;
case KMType.ENCRYPT:
case KMType.DECRYPT:
beginCipherOperation(op);
break;
case KMType.AGREE_KEY:
beginKeyAgreementOperation(op);
break;
default:
KMException.throwIt(KMError.UNIMPLEMENTED);
break;
}
short iv = KMType.INVALID_VALUE;
// If the data[IV] is required to be returned.
// As per VTS, for the decryption operation don't send the iv back.
if (data[IV] != KMType.INVALID_VALUE
&& op.getPurpose() != KMType.DECRYPT
&& op.getBlockMode() != KMType.ECB) {
iv = KMArray.instance((short) 1);
if (op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) {
// For AES/DES we are generate an random iv of length 16 bytes.
// While sending the iv back for DES/CBC mode of opeation only send
// 8 bytes back.
short ivBlob = KMByteBlob.instance((short) 8);
Util.arrayCopy(
KMByteBlob.cast(data[IV]).getBuffer(),
KMByteBlob.cast(data[IV]).getStartOff(),
KMByteBlob.cast(ivBlob).getBuffer(),
KMByteBlob.cast(ivBlob).getStartOff(),
(short) 8);
data[IV] = ivBlob;
}
KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV]));
} else {
iv = KMArray.instance((short) 0);
}
short macLen = 0;
if (op.getMacLength() != KMType.INVALID_VALUE) {
macLen = (short) (op.getMacLength() / 8);
}
short params = KMKeyParameters.instance(iv);
short resp = KMArray.instance((short) 5);
KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK));
KMArray.cast(resp).add((short) 1, params);
KMArray.cast(resp).add((short) 2, data[OP_HANDLE]);
KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode()));
KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen));
sendOutgoing(apdu, resp);
}
private void authorizePurpose(KMOperationState op) {
switch (op.getAlgorithm()) {
case KMType.AES:
case KMType.DES:
if (op.getPurpose() == KMType.SIGN
|| op.getPurpose() == KMType.VERIFY
|| op.getPurpose() == KMType.AGREE_KEY) {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
break;
case KMType.EC:
if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
break;
case KMType.HMAC:
if (op.getPurpose() == KMType.ENCRYPT
|| op.getPurpose() == KMType.DECRYPT
|| op.getPurpose() == KMType.AGREE_KEY) {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
break;
case KMType.RSA:
if (op.getPurpose() == KMType.AGREE_KEY) {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
break;
default:
break;
}
if (!KMEnumArrayTag.contains(KMType.PURPOSE, op.getPurpose(), data[HW_PARAMETERS])) {
KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
}
}
private void authorizeDigest(KMOperationState op) {
short digests =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]);
op.setDigest(KMType.DIGEST_NONE);
short param =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]);
if (param != KMType.INVALID_VALUE) {
if (KMEnumArrayTag.cast(param).length() != 1) {
KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
}
param = KMEnumArrayTag.cast(param).get((short) 0);
if (!KMEnumArrayTag.cast(digests).contains(param)) {
KMException.throwIt(KMError.INCOMPATIBLE_DIGEST);
}
op.setDigest((byte) param);
} else if (KMEnumArrayTag.contains(
KMType.PADDING, KMType.RSA_PKCS1_1_5_SIGN, data[KEY_PARAMETERS])) {
KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
}
short paramPadding =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]);
if (paramPadding != KMType.INVALID_VALUE) {
if (KMEnumArrayTag.cast(paramPadding).length() != 1) {
// TODO vts fails because it expects UNSUPPORTED_PADDING_MODE
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
paramPadding = KMEnumArrayTag.cast(paramPadding).get((short) 0);
}
switch (op.getAlgorithm()) {
case KMType.RSA:
if ((paramPadding == KMType.RSA_OAEP || paramPadding == KMType.RSA_PSS)
&& param == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
}
break;
case KMType.EC:
case KMType.HMAC:
if ((param == KMType.INVALID_VALUE && op.getPurpose() != KMType.AGREE_KEY)
|| !isDigestSupported(op.getAlgorithm(), op.getDigest())) {
KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
}
break;
default:
break;
}
}
private void authorizePadding(KMOperationState op) {
short paddings =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]);
op.setPadding(KMType.PADDING_NONE);
short param =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]);
if (param != KMType.INVALID_VALUE) {
if (KMEnumArrayTag.cast(param).length() != 1) {
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
param = KMEnumArrayTag.cast(param).get((short) 0);
if (!KMEnumArrayTag.cast(paddings).contains(param)) {
KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
}
}
switch (op.getAlgorithm()) {
case KMType.RSA:
if (param == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
if ((op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY)
&& param != KMType.PADDING_NONE
&& param != KMType.RSA_PSS
&& param != KMType.RSA_PKCS1_1_5_SIGN) {
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
if ((op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT)
&& param != KMType.PADDING_NONE
&& param != KMType.RSA_OAEP
&& param != KMType.RSA_PKCS1_1_5_ENCRYPT) {
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
if (param == KMType.PADDING_NONE && op.getDigest() != KMType.DIGEST_NONE) {
KMException.throwIt(KMError.INCOMPATIBLE_DIGEST);
}
if ((param == KMType.RSA_OAEP || param == KMType.RSA_PSS)
&& op.getDigest() == KMType.DIGEST_NONE) {
KMException.throwIt(KMError.INCOMPATIBLE_DIGEST);
}
if (op.getPurpose() == KMType.SIGN
|| op.getPurpose() == KMType.VERIFY
|| param == KMType.RSA_OAEP) {
// Digest is mandatory in these cases.
if (!isDigestSupported(op.getAlgorithm(), op.getDigest())) {
KMException.throwIt(KMError.UNSUPPORTED_DIGEST);
}
}
if (param == KMType.RSA_OAEP) {
short mgfDigest =
KMKeyParameters.findTag(
KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[KEY_PARAMETERS]);
if (mgfDigest != KMType.INVALID_VALUE) {
if (KMEnumArrayTag.cast(mgfDigest).length() != 1) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
mgfDigest = KMEnumArrayTag.cast(mgfDigest).get((short) 0);
if (mgfDigest == KMType.DIGEST_NONE) {
KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST);
}
} else {
mgfDigest = KMType.SHA1;
}
short mgfDigestHwParams =
KMKeyParameters.findTag(
KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[HW_PARAMETERS]);
if ((mgfDigestHwParams != KMType.INVALID_VALUE)
&& (!KMEnumArrayTag.cast(mgfDigestHwParams).contains(mgfDigest))) {
KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST);
}
if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) {
KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST);
}
op.setMgfDigest((byte) mgfDigest);
}
op.setPadding((byte) param);
break;
case KMType.DES:
case KMType.AES:
if (param == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE);
}
op.setPadding((byte) param);
break;
default:
break;
}
}
private void authorizeBlockModeAndMacLength(KMOperationState op) {
short param =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]);
if (param != KMType.INVALID_VALUE) {
if (KMEnumArrayTag.cast(param).length() != 1) {
KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE);
}
param = KMEnumArrayTag.cast(param).get((short) 0);
}
if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) {
if (!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])) {
KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE);
}
}
short macLen =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAC_LENGTH, data[KEY_PARAMETERS]);
switch (op.getAlgorithm()) {
case KMType.AES:
// Validate the block mode.
switch (param) {
case KMType.ECB:
case KMType.CBC:
case KMType.CTR:
case KMType.GCM:
break;
default:
KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE);
}
if (param == KMType.GCM) {
if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) {
KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
}
if (macLen == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.MISSING_MAC_LENGTH);
}
short minMacLen =
KMIntegerTag.getShortValue(
KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]);
if (minMacLen == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
if (macLen % 8 != 0
|| macLen > MAX_GCM_TAG_LENGTH_BITS
|| macLen < MIN_GCM_TAG_LENGTH_BITS) {
KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
}
if (macLen < minMacLen) {
KMException.throwIt(KMError.INVALID_MAC_LENGTH);
}
op.setMacLength(macLen);
}
if (param == KMType.CTR) {
if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) {
KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE);
}
}
break;
case KMType.DES:
// Validate the block mode.
switch (param) {
case KMType.ECB:
case KMType.CBC:
break;
default:
KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE);
}
if (param == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
break;
case KMType.HMAC:
short minMacLen =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]);
if (minMacLen == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
op.setMinMacLength(minMacLen);
if (macLen == KMType.INVALID_VALUE) {
if (op.getPurpose() == KMType.SIGN) {
KMException.throwIt(KMError.MISSING_MAC_LENGTH);
}
} else {
// MAC length may not be specified for verify.
if (op.getPurpose() == KMType.VERIFY) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
}
if (macLen % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) {
KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH);
}
if (macLen < minMacLen) {
KMException.throwIt(KMError.INVALID_MAC_LENGTH);
}
op.setMacLength(macLen);
}
break;
default:
break;
}
op.setBlockMode((byte) param);
}
private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) {
authorizePurpose(op);
authorizeDigest(op);
authorizePadding(op);
authorizeBlockModeAndMacLength(op);
if (!validateHwToken(data[HW_TOKEN], scratchPad)) {
data[HW_TOKEN] = KMType.INVALID_VALUE;
}
authorizeUserSecureIdAuthTimeout(op, scratchPad);
authorizeDeviceUnlock(scratchPad);
authorizeKeyUsageForCount(scratchPad);
KMTag.assertAbsence(
data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB);
// Validate early boot
// VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is
// present
if (kmDataStore.getEarlyBootEndedStatus()) {
KMTag.assertAbsence(
data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED);
}
// Authorize Caller Nonce - if caller nonce absent in key char and nonce present in
// key params then fail if it is not a Decrypt operation
data[IV] = KMType.INVALID_VALUE;
if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE)
&& KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE)
&& op.getPurpose() != KMType.DECRYPT) {
KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED);
}
short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]);
// If Nonce is present then check whether the size of nonce is correct.
if (nonce != KMType.INVALID_VALUE) {
data[IV] = KMByteTag.cast(nonce).getValue();
// For CBC mode - iv must be 8 bytes
if (op.getBlockMode() == KMType.CBC
&& op.getAlgorithm() == KMType.DES
&& KMByteBlob.cast(data[IV]).length() != 8) {
KMException.throwIt(KMError.INVALID_NONCE);
}
// For GCM mode - IV must be 12 bytes
if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) {
KMException.throwIt(KMError.INVALID_NONCE);
}
// For AES CBC and CTR modes IV must be 16 bytes
if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR)
&& op.getAlgorithm() == KMType.AES
&& KMByteBlob.cast(data[IV]).length() != 16) {
KMException.throwIt(KMError.INVALID_NONCE);
}
} else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) {
// For symmetric decryption iv is required
if (op.getPurpose() == KMType.DECRYPT
&& (op.getBlockMode() == KMType.CBC
|| op.getBlockMode() == KMType.GCM
|| op.getBlockMode() == KMType.CTR)) {
KMException.throwIt(KMError.MISSING_NONCE);
} else if (op.getBlockMode() == KMType.ECB) {
// For ECB we create zero length nonce
data[IV] = KMByteBlob.instance((short) 0);
} else if (op.getPurpose() == KMType.ENCRYPT) {
// For encrypt mode if nonce is absent then create random nonce of correct length
byte ivLen = 16;
if (op.getBlockMode() == KMType.GCM) {
ivLen = 12;
} else if (op.getAlgorithm() == KMType.DES) {
ivLen = 8;
}
data[IV] = KMByteBlob.instance(ivLen);
seProvider.newRandomNumber(
KMByteBlob.cast(data[IV]).getBuffer(),
KMByteBlob.cast(data[IV]).getStartOff(),
KMByteBlob.cast(data[IV]).length());
}
}
}
private void beginKeyAgreementOperation(KMOperationState op) {
if (op.getAlgorithm() != KMType.EC) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
op.setOperation(
seProvider.initAsymmetricOperation(
(byte) op.getPurpose(),
(byte) op.getAlgorithm(),
(byte) op.getPadding(),
(byte) op.getDigest(),
KMType.DIGEST_NONE, /* No MGF1 Digest */
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
null,
(short) 0,
(short) 0));
}
private void beginCipherOperation(KMOperationState op) {
switch (op.getAlgorithm()) {
case KMType.RSA:
try {
if (op.getPurpose() == KMType.DECRYPT) {
op.setOperation(
seProvider.initAsymmetricOperation(
(byte) op.getPurpose(),
(byte) op.getAlgorithm(),
(byte) op.getPadding(),
(byte) op.getDigest(),
(byte) op.getMgfDigest(),
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
KMByteBlob.cast(data[PUB_KEY]).getBuffer(),
KMByteBlob.cast(data[PUB_KEY]).getStartOff(),
KMByteBlob.cast(data[PUB_KEY]).length()));
} else {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
} catch (CryptoException exp) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
break;
case KMType.AES:
case KMType.DES:
if (op.getBlockMode() == KMType.GCM) {
op.setAesGcmUpdateStart();
}
try {
op.setOperation(
seProvider.initSymmetricOperation(
(byte) op.getPurpose(),
(byte) op.getAlgorithm(),
(byte) op.getDigest(),
(byte) op.getPadding(),
(byte) op.getBlockMode(),
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
KMByteBlob.cast(data[IV]).getBuffer(),
KMByteBlob.cast(data[IV]).getStartOff(),
KMByteBlob.cast(data[IV]).length(),
op.getMacLength()));
} catch (CryptoException exception) {
if (exception.getReason() == CryptoException.ILLEGAL_VALUE) {
KMException.throwIt(KMError.INVALID_ARGUMENT);
} else if (exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
}
}
}
private void beginTrustedConfirmationOperation(KMOperationState op) {
// Check for trusted confirmation - if required then set the signer in op state.
if (KMKeyParameters.findTag(
KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS])
!= KMType.INVALID_VALUE) {
op.setTrustedConfirmationSigner(
seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey()));
op.getTrustedConfirmationSigner()
.update(confirmationToken, (short) 0, (short) confirmationToken.length);
}
}
private void beginSignVerifyOperation(KMOperationState op) {
switch (op.getAlgorithm()) {
case KMType.RSA:
try {
if (op.getPurpose() == KMType.SIGN) {
op.setOperation(
seProvider.initAsymmetricOperation(
(byte) op.getPurpose(),
(byte) op.getAlgorithm(),
(byte) op.getPadding(),
(byte) op.getDigest(),
KMType.DIGEST_NONE, /* No MGF Digest */
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
KMByteBlob.cast(data[PUB_KEY]).getBuffer(),
KMByteBlob.cast(data[PUB_KEY]).getStartOff(),
KMByteBlob.cast(data[PUB_KEY]).length()));
} else {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
} catch (CryptoException exp) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
break;
case KMType.EC:
try {
if (op.getPurpose() == KMType.SIGN) {
op.setOperation(
seProvider.initAsymmetricOperation(
(byte) op.getPurpose(),
(byte) op.getAlgorithm(),
(byte) op.getPadding(),
(byte) op.getDigest(),
KMType.DIGEST_NONE, /* No MGF Digest */
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
null,
(short) 0,
(short) 0));
} else {
KMException.throwIt(KMError.UNSUPPORTED_PURPOSE);
}
} catch (CryptoException exp) {
// Javacard does not support NO digest based signing.
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
break;
case KMType.HMAC:
// As per Keymaster HAL documentation, the length of the Hmac output can
// be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no
// such provision to control the length of the Hmac output using JavaCard
// crypto APIs and the current implementation always returns 32 bytes
// length of Hmac output. So to provide support to TAG_MAC_LENGTH
// feature, we truncate the output signature to TAG_MAC_LENGTH and return
// the truncated signature back to the caller. At the time of verfication
// we again compute the signature of the plain text input, truncate it to
// TAG_MAC_LENGTH and compare it with the input signature for
// verification. So this is the reason we are using KMType.SIGN directly
// instead of using op.getPurpose().
try {
op.setOperation(
seProvider.initSymmetricOperation(
(byte) KMType.SIGN,
(byte) op.getAlgorithm(),
(byte) op.getDigest(),
(byte) op.getPadding(),
(byte) op.getBlockMode(),
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
null,
(short) 0,
(short) 0,
(short) 0));
} catch (CryptoException exp) {
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
}
break;
default:
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
break;
}
}
private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) {
short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId();
if (!KMInteger.cast(secureUserId).isZero()) {
if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) {
return true;
}
}
short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId();
if (!KMInteger.cast(authenticatorId).isZero()) {
if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) {
return true;
}
}
return false;
}
public boolean hwAuthTypeMatches(
byte[] buf, short off, short len, byte[] scratchPad, short scratchOff) {
Util.arrayFillNonAtomic(scratchPad, scratchOff, (short) (2 * KMInteger.UINT_32), (byte) 0);
short enumPtr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType();
if (KMInteger.UINT_32 != KMEnum.cast(enumPtr).value(scratchPad, scratchOff)) {
return false;
}
Util.arrayCopyNonAtomic(
buf, off, scratchPad, (short) (scratchOff + 2 * KMInteger.UINT_32 - len), len);
short highShort = Util.getShort(scratchPad, scratchOff);
short lowShort = Util.getShort(scratchPad, (short) (scratchOff + 2));
short otherHighShort = Util.getShort(scratchPad, (short) (scratchOff + KMInteger.UINT_32));
short otherLowShort = Util.getShort(scratchPad, (short) (scratchOff + KMInteger.UINT_32 + 2));
return (0 != (lowShort & otherLowShort) || 0 != (highShort & otherHighShort));
}
private boolean authTokenMatches(
short userSecureIdsPtr,
byte[] buf,
short off,
short len,
byte[] scratchPad,
short scratchOff) {
if (data[HW_TOKEN] == KMType.INVALID_VALUE) {
return false;
}
if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) {
return false;
}
// check auth type
return hwAuthTypeMatches(buf, off, len, scratchPad, scratchOff);
}
private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) {
short authTime;
short authType;
// Authorize User Secure Id and Auth timeout
short userSecureIdPtr =
KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]);
if (userSecureIdPtr != KMType.INVALID_VALUE) {
// Authentication required.
if (KMType.INVALID_VALUE
!= KMKeyParameters.findTag(
KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) {
// Key has both USER_SECURE_ID and NO_AUTH_REQUIRED
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
// authenticator type must be provided.
if (KMType.INVALID_VALUE
== (authType =
KMKeyParameters.findTag(
KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) {
// Authentication required, but no auth type found.
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
short len = KMEnumTag.cast(authType).value(scratchPad, (short) 0);
short authTimeoutTagPtr =
KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]);
if (authTimeoutTagPtr != KMType.INVALID_VALUE) {
// authenticate user
if (!authTokenMatches(userSecureIdPtr, scratchPad, (short) 0, len, scratchPad, len)) {
KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED);
}
authTimeoutTagPtr =
KMKeyParameters.findTag(
KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]);
if (authTimeoutTagPtr == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue();
// set the one time auth
op.setOneTimeAuthReqd(true);
// set the authentication time stamp in operation state
authTime =
addIntegers(
authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad);
op.setAuthTime(
KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff());
// auth time validation will happen in update or finish
op.setAuthTimeoutValidated(false);
} else {
// auth per operation required
// store user secure id and authType in OperationState.
op.setUserSecureId(userSecureIdPtr);
op.setAuthType(scratchPad, (short) 0, len);
// set flags
op.setOneTimeAuthReqd(false);
op.setAuthPerOperationReqd(true);
}
}
}
private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) {
// The challenge, userId and authenticatorId, authenticatorType and timestamp
// are in network order (big-endian).
short len = 0;
// add 0
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
len = 1;
// concatenate challenge - 8 bytes
short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
// concatenate user id - 8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getUserId();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
// concatenate authenticator id - 8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
// concatenate authenticator type - 4 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType();
KMEnum.cast(ptr).value(scratchPad, len);
len += KMInteger.UINT_32;
// concatenate timestamp -8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp();
KMInteger.cast(ptr)
.value(
scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
ptr = KMHardwareAuthToken.cast(hwToken).getMac();
return seProvider.hmacVerify(
kmDataStore.getComputedHmacKey(),
scratchPad,
(short) 0,
len,
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
}
private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) {
// The challenge, userId and authenticatorId values are in little endian order,
// but authenticatorType and timestamp are in network order (big-endian).
short len = 0;
// add 0
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0);
len = 1;
// concatenate challenge - 8 bytes
short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge();
KMInteger.cast(ptr).toLittleEndian(scratchPad, len);
len += KMInteger.UINT_64;
// concatenate user id - 8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getUserId();
KMInteger.cast(ptr).toLittleEndian(scratchPad, len);
len += KMInteger.UINT_64;
// concatenate authenticator id - 8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId();
KMInteger.cast(ptr).toLittleEndian(scratchPad, len);
len += KMInteger.UINT_64;
// concatenate authenticator type - 4 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType();
KMEnum.cast(ptr).value(scratchPad, len);
len += KMInteger.UINT_32;
// concatenate timestamp - 8 bytes
ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp();
KMInteger.cast(ptr)
.value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length())));
len += KMInteger.UINT_64;
ptr = KMHardwareAuthToken.cast(hwToken).getMac();
return seProvider.hmacVerify(
kmDataStore.getComputedHmacKey(),
scratchPad,
(short) 0,
len,
KMByteBlob.cast(ptr).getBuffer(),
KMByteBlob.cast(ptr).getStartOff(),
KMByteBlob.cast(ptr).length());
}
private boolean validateHwToken(short hwToken, byte[] scratchPad) {
// CBOR Encoding is always big endian
short ptr = KMHardwareAuthToken.cast(hwToken).getMac();
// If mac length is zero then token is empty.
if (KMByteBlob.cast(ptr).length() == 0) {
return false;
}
if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) {
return verifyHwTokenMacInLittleEndian(hwToken, scratchPad);
} else {
return verifyHwTokenMacInBigEndian(hwToken, scratchPad);
}
}
private short importKeyCmd(APDU apdu) {
short cmd = KMArray.instance((short) 6);
// Arguments
short params = KMKeyParameters.expAny();
KMArray.cast(cmd).add((short) 0, params);
KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT));
KMArray.cast(cmd).add((short) 2, KMByteBlob.exp());
KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key
KMArray.cast(cmd).add((short) 4, params); // attest key params
KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer
return receiveIncoming(apdu, cmd);
}
private void processImportKeyCmd(APDU apdu) {
// Receive the incoming request fully from the host into buffer.
short cmd = importKeyCmd(apdu);
byte[] scratchPad = apdu.getBuffer();
data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0);
short keyFmt = KMArray.cast(cmd).get((short) 1);
data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2);
data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3);
data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4);
data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5);
keyFmt = KMEnum.cast(keyFmt).getVal();
data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty.
data[ORIGIN] = KMType.IMPORTED;
importKey(apdu, keyFmt, scratchPad);
}
private void validateImportKey(short params, short keyFmt) {
short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, params);
// ATTEST_KEY cannot be combined with any other purpose.
if (attKeyPurpose != KMType.INVALID_VALUE
&& KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)
&& KMEnumArrayTag.cast(attKeyPurpose).length() > 1) {
KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
}
// Rollback protection not supported
KMTag.assertAbsence(
params,
KMType.BOOL_TAG,
KMType.ROLLBACK_RESISTANCE,
KMError.ROLLBACK_RESISTANCE_UNAVAILABLE);
// As per specification, Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is
// provided to IKeyMintDevice::importKey
KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED);
// Check if the tags are supported.
if (KMKeyParameters.hasUnsupportedTags(params)) {
KMException.throwIt(KMError.UNSUPPORTED_TAG);
}
// Algorithm must be present
KMTag.assertPresence(params, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT);
short alg = KMEnumTag.getValue(KMType.ALGORITHM, params);
// key format must be raw if aes, des or hmac and pkcs8 for rsa and ec.
if ((alg == KMType.AES || alg == KMType.DES || alg == KMType.HMAC) && keyFmt != KMType.RAW) {
KMException.throwIt(KMError.UNIMPLEMENTED);
}
if ((alg == KMType.RSA || alg == KMType.EC) && keyFmt != KMType.PKCS8) {
KMException.throwIt(KMError.UNIMPLEMENTED);
}
}
private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) {
validateImportKey(data[KEY_PARAMETERS], keyFmt);
// Check algorithm and dispatch to appropriate handler.
short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
switch (alg) {
case KMType.RSA:
importRSAKey(scratchPad);
break;
case KMType.AES:
importAESKey(scratchPad);
break;
case KMType.DES:
importTDESKey(scratchPad);
break;
case KMType.HMAC:
importHmacKey(scratchPad);
break;
case KMType.EC:
importECKeys(scratchPad);
break;
default:
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
break;
}
makeKeyCharacteristics(scratchPad);
KMAttestationCert cert =
generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad);
createEncryptedKeyBlob(scratchPad);
sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]);
}
private void importECKeys(byte[] scratchPad) {
// Decode key material
KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
short keyBlob = pkcs8.decodeEc(data[IMPORTED_KEY_BLOB]);
data[PUB_KEY] = KMArray.cast(keyBlob).get((short) 0);
data[SECRET] = KMArray.cast(keyBlob).get((short) 1);
// initialize 256 bit p256 key for given private key and public key.
short index = 0;
// check whether the key size tag is present in key parameters.
short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8);
short keySize =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
if (keySize != KMType.INVALID_VALUE) {
// As per NIST.SP.800-186 page 9, secret for 256 curve should be between
// 256-383
if (((256 <= SecretLen) && (383 >= SecretLen)) ^ keySize == 256) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
if (keySize != 256) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
} else {
if ((256 > SecretLen) || (383 < SecretLen)) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
// add the key size to scratchPad
keySize = KMInteger.uint_16((short) 256);
keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keySize);
Util.setShort(scratchPad, index, keySize);
index += 2;
}
// check the curve if present in key parameters.
short curve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]);
if (curve != KMType.INVALID_VALUE) {
// As per NIST.SP.800-186 page 9, secret length for 256 curve should be between
// 256-383
if (((256 <= SecretLen) && (383 >= SecretLen)) ^ curve == KMType.P_256) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
if (curve != KMType.P_256) {
KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE);
}
} else {
if ((256 > SecretLen) || (383 < SecretLen)) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
// add the curve to scratchPad
curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256);
Util.setShort(scratchPad, index, curve);
index += 2;
}
// Check whether key can be created
seProvider.importAsymmetricKey(
KMType.EC,
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
KMByteBlob.cast(data[PUB_KEY]).getBuffer(),
KMByteBlob.cast(data[PUB_KEY]).getStartOff(),
KMByteBlob.cast(data[PUB_KEY]).length());
// add scratch pad to key parameters
updateKeyParameters(scratchPad, index);
data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
}
private void importHmacKey(byte[] scratchPad) {
// Get Key
data[SECRET] = data[IMPORTED_KEY_BLOB];
// create HMAC key of up to 512 bit
short index = 0; // index in scratchPad for update params
// check the keysize tag if present in key parameters.
short keysize =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
if (keysize != KMType.INVALID_VALUE) {
if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
if (keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
} else {
// add the key size to scratchPad
keysize = (short) (KMByteBlob.length(data[SECRET]) * 8);
if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
keysize = KMInteger.uint_16(keysize);
short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
Util.setShort(scratchPad, index, keySizeTag);
index += 2;
}
// Check whether key can be created
seProvider.importSymmetricKey(
KMType.HMAC,
keysize,
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length());
// update the key parameters list
updateKeyParameters(scratchPad, index);
// validate HMAC Key parameters
validateHmacKey();
data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
}
private void importTDESKey(byte[] scratchPad) {
// Decode Key Material
data[SECRET] = data[IMPORTED_KEY_BLOB];
short index = 0; // index in scratchPad for update params
// check the keysize tag if present in key parameters.
short keysize =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
if (keysize != KMType.INVALID_VALUE) {
if (keysize != 168) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
if (192 != (short) (8 * KMByteBlob.length(data[SECRET]))) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
} else {
keysize = (short) (KMByteBlob.length(data[SECRET]) * 8);
if (keysize != 192) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
// add the key size to scratchPad
keysize = KMInteger.uint_16((short) 168);
short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
Util.setShort(scratchPad, index, keysizeTag);
index += 2;
}
// Read Minimum Mac length - it must not be present
KMTag.assertAbsence(
data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG);
// Check whether key can be created
seProvider.importSymmetricKey(
KMType.DES,
keysize,
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length());
// update the key parameters list
updateKeyParameters(scratchPad, index);
data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
}
private void validateAesKeySize(short keySizeBits) {
if (keySizeBits != 128 && keySizeBits != 256) {
KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE);
}
}
private void importAESKey(byte[] scratchPad) {
// Get Key
data[SECRET] = data[IMPORTED_KEY_BLOB];
// create 128 or 256 bit AES key
short index = 0; // index in scratchPad for update params
// check the keysize tag if present in key parameters.
short keysize =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
if (keysize != KMType.INVALID_VALUE) {
if (keysize != (short) (8 * KMByteBlob.length(data[SECRET]))) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
validateAesKeySize(keysize);
} else {
// add the key size to scratchPad
keysize = (short) (8 * KMByteBlob.cast(data[SECRET]).length());
validateAesKeySize(keysize);
keysize = KMInteger.uint_16(keysize);
short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
Util.setShort(scratchPad, index, keysizeTag);
index += 2;
}
// Check whether key can be created
seProvider.importSymmetricKey(
KMType.AES,
keysize,
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length());
// update the key parameters list
updateKeyParameters(scratchPad, index);
// validate AES Key parameters
validateAESKey();
data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE);
}
private void importRSAKey(byte[] scratchPad) {
// Decode key material
KMAsn1Parser pkcs8 = KMAsn1Parser.instance();
short keyblob = pkcs8.decodeRsa(data[IMPORTED_KEY_BLOB]);
data[PUB_KEY] = KMArray.cast(keyblob).get((short) 0);
short pubKeyExp = KMArray.cast(keyblob).get((short) 1);
data[SECRET] = KMArray.cast(keyblob).get((short) 2);
if (F4.length != KMByteBlob.cast(pubKeyExp).length()) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
if (Util.arrayCompare(
F4,
(short) 0,
KMByteBlob.cast(pubKeyExp).getBuffer(),
KMByteBlob.cast(pubKeyExp).getStartOff(),
(short) F4.length)
!= 0) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
short index = 0; // index in scratchPad for update parameters.
// validate public exponent if present in key params - it must be 0x010001
short len =
KMIntegerTag.getValue(
scratchPad,
(short) 10, // using offset 10 as first 10 bytes reserved for update params
KMType.ULONG_TAG,
KMType.RSA_PUBLIC_EXPONENT,
data[KEY_PARAMETERS]);
if (len != KMTag.INVALID_VALUE) {
if (len != 4
|| Util.getShort(scratchPad, (short) 10) != 0x01
|| Util.getShort(scratchPad, (short) 12) != 0x01) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
} else {
// add public exponent to scratchPad
Util.setShort(scratchPad, (short) 10, (short) 0x01);
Util.setShort(scratchPad, (short) 12, (short) 0x01);
pubKeyExp = KMInteger.uint_32(scratchPad, (short) 10);
pubKeyExp = KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, pubKeyExp);
Util.setShort(scratchPad, index, pubKeyExp);
index += 2;
}
// check the keysize tag if present in key parameters.
short keysize =
KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]);
short kSize = (short) (KMByteBlob.length(data[PUB_KEY]) * 8);
if (keysize != KMType.INVALID_VALUE) {
if (keysize != 2048 || (keysize != kSize)) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
} else {
if (2048 != kSize) {
KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH);
}
// add the key size to scratchPad
keysize = KMInteger.uint_16((short) 2048);
keysize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize);
Util.setShort(scratchPad, index, keysize);
index += 2;
}
// Check whether key can be created
seProvider.importAsymmetricKey(
KMType.RSA,
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
KMByteBlob.cast(data[PUB_KEY]).getBuffer(),
KMByteBlob.cast(data[PUB_KEY]).getStartOff(),
KMByteBlob.cast(data[PUB_KEY]).length());
// update the key parameters list
updateKeyParameters(scratchPad, index);
// validate RSA Key parameters
validateRSAKey(scratchPad);
data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE);
KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]);
}
private void updateKeyParameters(byte[] newParams, short len) {
if (len == 0) {
return; // nothing to update
}
// Create Update Param array and copy current params
short params = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals();
len = (short) (KMArray.cast(params).length() + (short) (len / 2));
short updatedParams = KMArray.instance(len); // update params
len = KMArray.cast(params).length();
short index = 0;
// copy the existing key parameters to updated array
while (index < len) {
short tag = KMArray.cast(params).get(index);
KMArray.cast(updatedParams).add(index, tag);
index++;
}
// copy new parameters to updated array
len = KMArray.cast(updatedParams).length();
short newParamIndex = 0; // index in ptrArr
while (index < len) {
short tag = Util.getShort(newParams, newParamIndex);
KMArray.cast(updatedParams).add(index, tag);
index++;
newParamIndex += 2;
}
// replace with updated key parameters.
data[KEY_PARAMETERS] = KMKeyParameters.instance(updatedParams);
}
private short initStrongBoxCmd(APDU apdu) {
short cmd = KMArray.instance((short) 3);
KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // OS version
KMArray.cast(cmd).add((short) 1, KMInteger.exp()); // OS patch level
KMArray.cast(cmd).add((short) 2, KMInteger.exp()); // Vendor patch level
return receiveIncoming(apdu, cmd);
}
// This command is executed to set the boot parameters.
// releaseAllOperations has to be called on every boot, so
// it is called from inside initStrongBoxCmd. Later in future if
// initStrongBoxCmd is removed, then make sure that releaseAllOperations
// is moved to a place where it is called on every boot.
private void processInitStrongBoxCmd(APDU apdu) {
short cmd = initStrongBoxCmd(apdu);
short osVersion = KMArray.cast(cmd).get((short) 0);
short osPatchLevel = KMArray.cast(cmd).get((short) 1);
short vendorPatchLevel = KMArray.cast(cmd).get((short) 2);
setOsVersion(osVersion);
setOsPatchLevel(osPatchLevel);
setVendorPatchLevel(vendorPatchLevel);
kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS);
}
public void reboot() {
// flag to maintain early boot ended state
kmDataStore.setEarlyBootEndedStatus(false);
// Clear all the operation state.
releaseAllOperations();
// Hmac is cleared, so generate a new Hmac nonce.
initHmacNonceAndSeed();
// Clear all auth tags.
kmDataStore.removeAllAuthTags();
}
protected void setOsVersion(short version) {
kmDataStore.setOsVersion(
KMInteger.cast(version).getBuffer(),
KMInteger.cast(version).getStartOff(),
KMInteger.cast(version).length());
}
protected void setOsPatchLevel(short patch) {
kmDataStore.setOsPatch(
KMInteger.cast(patch).getBuffer(),
KMInteger.cast(patch).getStartOff(),
KMInteger.cast(patch).length());
}
protected void setVendorPatchLevel(short patch) {
kmDataStore.setVendorPatchLevel(
KMInteger.cast(patch).getBuffer(),
KMInteger.cast(patch).getStartOff(),
KMInteger.cast(patch).length());
}
private short generateKeyCmd(APDU apdu) {
short params = KMKeyParameters.expAny();
short blob = KMByteBlob.exp();
// Array of expected arguments
short cmd = KMArray.instance((short) 4);
KMArray.cast(cmd).add((short) 0, params); // key params
KMArray.cast(cmd).add((short) 1, blob); // attest key
KMArray.cast(cmd).add((short) 2, params); // attest key params
KMArray.cast(cmd).add((short) 3, blob); // issuer
return receiveIncoming(apdu, cmd);
}
private void processGenerateKey(APDU apdu) {
// Receive the incoming request fully from the host into buffer.
short cmd = generateKeyCmd(apdu);
// Re-purpose the apdu buffer as scratch pad.
byte[] scratchPad = apdu.getBuffer();
data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0);
data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1);
data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2);
data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3);
data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty.
// ROLLBACK_RESISTANCE not supported.
KMTag.assertAbsence(
data[KEY_PARAMETERS],
KMType.BOOL_TAG,
KMType.ROLLBACK_RESISTANCE,
KMError.ROLLBACK_RESISTANCE_UNAVAILABLE);
// Algorithm must be present
KMTag.assertPresence(
data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT);
// Check if the tags are supported.
if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) {
KMException.throwIt(KMError.UNSUPPORTED_TAG);
}
short attKeyPurpose =
KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]);
// ATTEST_KEY cannot be combined with any other purpose.
if (attKeyPurpose != KMType.INVALID_VALUE
&& KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)
&& KMEnumArrayTag.cast(attKeyPurpose).length() > 1) {
KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE);
}
short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]);
// Check algorithm and dispatch to appropriate handler.
switch (alg) {
case KMType.RSA:
generateRSAKey(scratchPad);
break;
case KMType.AES:
generateAESKey(scratchPad);
break;
case KMType.DES:
generateTDESKey(scratchPad);
break;
case KMType.HMAC:
generateHmacKey(scratchPad);
break;
case KMType.EC:
generateECKeys(scratchPad);
break;
default:
KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM);
break;
}
// create key blob and associated attestation.
data[ORIGIN] = KMType.GENERATED;
makeKeyCharacteristics(scratchPad);
// construct the certificate and place the encoded data in data[CERTIFICATE]
KMAttestationCert cert =
generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad);
createEncryptedKeyBlob(scratchPad);
sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]);
}
private short getApplicationId(short params) {
short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, params);
if (appId != KMTag.INVALID_VALUE) {
appId = KMByteTag.cast(appId).getValue();
if (KMByteBlob.cast(appId).length() == 0) {
// Treat empty as INVALID.
return KMType.INVALID_VALUE;
}
}
return appId;
}
private short getApplicationData(short params) {
short appData = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, params);
if (appData != KMTag.INVALID_VALUE) {
appData = KMByteTag.cast(appData).getValue();
if (KMByteBlob.cast(appData).length() == 0) {
// Treat empty as INVALID.
return KMType.INVALID_VALUE;
}
}
return appData;
}
private short getAttestationMode(short attKeyBlob, short attChallenge) {
short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]);
short mode = KMType.NO_CERT;
if (KMEnumTag.cast(alg).getValue() != KMType.RSA
&& KMEnumTag.cast(alg).getValue() != KMType.EC) {
return mode;
}
// If attestation keyblob preset
if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) {
// No attestation challenge present then it is an error
if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) {
KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING);
} else {
mode = KMType.ATTESTATION_CERT;
}
} else { // no attestation key blob
// Attestation challenge present then it is an error because no factory provisioned attest key
if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) {
KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED);
} else if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS])
|| KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) {
// The Purpose value can be read from either data[HW_PARAMETERS] or data[KEY_PARAMETERS]
// as the values will be same, and they are cryptographically bound.
mode = KMType.SELF_SIGNED_CERT;
} else {
mode = KMType.FAKE_CERT;
}
}
return mode;
}
private KMAttestationCert generateAttestation(
short attKeyBlob, short attKeyParam, byte[] scratchPad) {
// 1) If attestation key is present and attestation challenge is absent then it is an error.
// 2) If attestation key is absent and attestation challenge is present then it is an error as
// factory provisioned attestation key is not supported.
// 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it
// is an error.
// 4) If the generated/imported keys are RSA or EC then validity period must be specified.
// Device Unique Attestation is not supported.
short heapStart = repository.getHeapIndex();
KMTag.assertAbsence(
data[KEY_PARAMETERS],
KMType.BOOL_TAG,
KMType.DEVICE_UNIQUE_ATTESTATION,
KMError.CANNOT_ATTEST_IDS);
// Read attestation challenge if present
short attChallenge =
KMKeyParameters.findTag(
KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]);
if (attChallenge != KMType.INVALID_VALUE) {
attChallenge = KMByteTag.cast(attChallenge).getValue();
}
// No attestation required for symmetric keys
short mode = getAttestationMode(attKeyBlob, attChallenge);
KMAttestationCert cert = null;
switch (mode) {
case KMType.ATTESTATION_CERT:
cert =
makeAttestationCert(
attKeyBlob, attKeyParam, attChallenge, data[ATTEST_KEY_ISSUER], scratchPad);
break;
case KMType.SELF_SIGNED_CERT:
cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY], mode, scratchPad);
break;
case KMType.FAKE_CERT:
// Generate certificate with no signature.
cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], mode, scratchPad);
break;
default:
data[CERTIFICATE] = KMType.INVALID_VALUE;
return null;
}
// Certificate Data is converted to cbor and written to the end of the stack.
short certData = repository.allocReclaimableMemory(MAX_CERT_SIZE);
// Leave first 4 bytes for Array header and ByteBlob header.
cert.buffer(repository.getHeap(), (short) (certData + 4), (short) (MAX_CERT_SIZE - 4));
// Build the certificate - this will sign the cert
cert.build();
// Certificate is now built so the data in the heap starting from heapStart to the current
// heap index can be reused. So resetting the heap index to heapStart.
repository.setHeapIndex(heapStart);
data[CERTIFICATE] = certData;
return cert;
}
// Encodes KeyCharacteristics at the end of the heap
private void encodeKeyCharacteristics(short keyChars) {
byte[] buffer = repository.getHeap();
short prevReclaimIndex = repository.getHeapReclaimIndex();
short ptr = repository.allocReclaimableMemory(MAX_KEY_CHARS_SIZE);
short len = encoder.encode(keyChars, buffer, ptr, prevReclaimIndex, MAX_KEY_CHARS_SIZE);
// shift the encoded KeyCharacteristics data towards the right till the data[CERTIFICATE]
// offset.
Util.arrayCopyNonAtomic(buffer, ptr, buffer, (short) (ptr + (MAX_KEY_CHARS_SIZE - len)), len);
// Reclaim the unused memory.
repository.reclaimMemory((short) (MAX_KEY_CHARS_SIZE - len));
}
// Encodes KeyBlob at the end of the heap
private void encodeKeyBlob(short keyBlobPtr) {
// allocate reclaimable memory.
byte[] buffer = repository.getHeap();
short prevReclaimIndex = repository.getHeapReclaimIndex();
short top = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE);
short keyBlob = encoder.encode(keyBlobPtr, buffer, top, prevReclaimIndex, MAX_KEYBLOB_SIZE);
Util.arrayCopyNonAtomic(
repository.getHeap(),
top,
repository.getHeap(),
(short) (top + MAX_KEYBLOB_SIZE - keyBlob),
keyBlob);
short newTop = (short) (top + MAX_KEYBLOB_SIZE - keyBlob);
// Encode the KeyBlob array inside a ByteString. Get the length of
// the ByteString header.
short encodedBytesLength = encoder.getEncodedBytesLength(keyBlob);
newTop -= encodedBytesLength;
encoder.encodeByteBlobHeader(keyBlob, buffer, newTop, encodedBytesLength);
// Reclaim unused memory.
repository.reclaimMemory((short) (newTop - top));
}
private short readKeyBlobVersion(short keyBlob) {
short version = KMType.INVALID_VALUE;
try {
version =
decoder.readKeyblobVersion(
KMByteBlob.cast(keyBlob).getBuffer(),
KMByteBlob.cast(keyBlob).getStartOff(),
KMByteBlob.cast(keyBlob).length());
if (version == KMType.INVALID_VALUE) {
// If Version is not present. Then it is either an old KeyBlob or
// corrupted KeyBlob.
version = 0;
} else {
version = KMInteger.cast(version).getShort();
if (version > KEYBLOB_CURRENT_VERSION || version < 0) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
}
} catch (Exception e) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
return version;
}
private void readKeyBlobParams(short version, short parsedKeyBlob) {
data[KEY_BLOB] = parsedKeyBlob;
// initialize data
switch (version) {
case (short) 0:
data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 0);
data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 1);
data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 2);
data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 3);
data[PUB_KEY] = KMType.INVALID_VALUE;
if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V0) {
data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 4);
}
// Set the data[KEY_BLOB_VERSION_DATA_OFFSET] with integer value of 0 so
// that it will used at later point of time.
data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_8((byte) 0);
break;
case (short) 1:
data[KEY_BLOB_VERSION_DATA_OFFSET] = KMArray.cast(parsedKeyBlob).get((short) 0);
data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 1);
data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 2);
data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 3);
data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 4);
data[PUB_KEY] = KMType.INVALID_VALUE;
if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V1) {
data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 5);
}
break;
case (short) 2:
case (short) 3:
data[SECRET] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_SECRET);
data[NONCE] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_NONCE);
data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_AUTH_TAG);
data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PARAMS);
data[KEY_BLOB_VERSION_DATA_OFFSET] =
KMArray.cast(parsedKeyBlob).get(KEY_BLOB_VERSION_OFFSET);
data[CUSTOM_TAGS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_CUSTOM_TAGS);
data[PUB_KEY] = KMType.INVALID_VALUE;
if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V2_V3) {
data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PUB_KEY);
}
break;
default:
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
}
// Decode the KeyBlob from CBOR structures to the sub types of KMType.
private void decodeKeyBlob(short version, short keyBlob) {
// Decode KeyBlob and read the KeyBlob params based on the version.
short parsedBlob =
decoder.decodeArray(
createKeyBlobExp(version),
KMByteBlob.cast(keyBlob).getBuffer(),
KMByteBlob.cast(keyBlob).getStartOff(),
KMByteBlob.cast(keyBlob).length());
short minArraySize = 0;
switch (version) {
case 0:
minArraySize = SYM_KEY_BLOB_SIZE_V0;
break;
case 1:
minArraySize = SYM_KEY_BLOB_SIZE_V1;
break;
case 2:
case 3:
minArraySize = SYM_KEY_BLOB_SIZE_V2_V3;
break;
default:
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
// KeyBlob size should not be less than the minimum KeyBlob size.
if (KMArray.cast(parsedBlob).length() < minArraySize) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
readKeyBlobParams(version, parsedBlob);
}
// Decrypts the secret key in the KeyBlob. The secret can be a Symmetric or Asymmetric key.
private void processDecryptSecret(short version, short appId, short appData, byte[] scratchPad) {
data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced();
data[SB_PARAMETERS] =
KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced();
data[SW_PARAMETERS] =
KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced();
data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]);
data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, data[ROT], scratchPad);
// Decrypt Secret and verify auth tag
decryptSecret(scratchPad, version);
short keyBlobSecretOff = 0;
switch (version) {
case 0:
// V0 KeyBlob
// KEY_BLOB = [
// SECRET,
// NONCE,
// AUTH_TAG,
// KEY_CHARACTERISTICS,
// PUBKEY
// ]
keyBlobSecretOff = (short) 0;
break;
case 1:
// V1 KeyBlob
// KEY_BLOB = [
// VERSION,
// SECRET,
// NONCE,
// AUTH_TAG,
// KEY_CHARACTERISTICS,
// PUBKEY
// ]
keyBlobSecretOff = (short) 1;
break;
case 2:
case 3:
// V2 KeyBlob
// KEY_BLOB = [
// VERSION,
// SECRET,
// NONCE,
// AUTH_TAG,
// KEY_CHARACTERISTICS,
// CUSTOM_TAGS,
// PUBKEY
// ]
keyBlobSecretOff = KEY_BLOB_SECRET;
break;
default:
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
KMArray.cast(data[KEY_BLOB]).add(keyBlobSecretOff, data[SECRET]);
}
private void parseEncryptedKeyBlob(
short keyBlob, short appId, short appData, byte[] scratchPad, short version) {
// make root of trust blob
data[ROT] = readROT(scratchPad, version);
if (data[ROT] == KMType.INVALID_VALUE) {
KMException.throwIt(KMError.UNKNOWN_ERROR);
}
try {
decodeKeyBlob(version, keyBlob);
processDecryptSecret(version, appId, appData, scratchPad);
} catch (Exception e) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
}
private void decryptSecret(byte[] scratchPad, short version) {
// derive master key - stored in derivedKey
short len;
short authDataOff = 0;
short authDataLen = 0;
byte[] authDataBuff = null;
switch (version) {
case 3:
len = deriveKey(scratchPad);
break;
case 2:
case 1:
case 0:
makeAuthData(version, scratchPad);
len = deriveKeyForOldKeyBlobs(scratchPad);
authDataBuff = repository.getHeap();
authDataOff = data[AUTH_DATA];
authDataLen = data[AUTH_DATA_LENGTH];
break;
default:
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
if (!seProvider.aesGCMDecrypt(
KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(),
KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(),
KMByteBlob.cast(data[DERIVED_KEY]).length(),
KMByteBlob.cast(data[SECRET]).getBuffer(),
KMByteBlob.cast(data[SECRET]).getStartOff(),
KMByteBlob.cast(data[SECRET]).length(),
scratchPad,
(short) 0,
KMByteBlob.cast(data[NONCE]).getBuffer(),
KMByteBlob.cast(data[NONCE]).getStartOff(),
KMByteBlob.cast(data[NONCE]).length(),
authDataBuff,
authDataOff,
authDataLen,
KMByteBlob.cast(data[AUTH_TAG]).getBuffer(),
KMByteBlob.cast(data[AUTH_TAG]).getStartOff(),
KMByteBlob.cast(data[AUTH_TAG]).length())) {
KMException.throwIt(KMError.INVALID_KEY_BLOB);
}
// Copy the decrypted secret
data[SECRET] =
KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length());
}
private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) {
Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0);
Util.arrayCopyNonAtomic(
KMInteger.cast(authTime).getBuffer(),
KMInteger.cast(authTime).getStartOff(),
scratchPad,
(short) (8 - KMInteger.cast(timeStamp).length()),
KMInteger.cast(timeStamp).length());
// Copy timestamp to scratchpad
Util.arrayCopyNonAtomic(
KMInteger.cast(timeStamp).getBuffer(),
KMInteger.cast(timeStamp).getStartOff(),
scratchPad,
(short) (16 - KMInteger.cast(timeStamp).length()),
KMInteger.cast(timeStamp).length());
// add authTime in millis to timestamp.
KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16);
return KMInteger.uint_64(scratchPad, (short) 16);
}
public void powerReset() {
// TODO handle power reset signal.
releaseAllOperations();
resetWrappingKey();
}
private void updateTrustedConfirmationOperation(KMOperationState op) {
if (op.isTrustedConfirmationRequired()) {
op.getTrustedConfirmationSigner()
.update(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length());
}
}
private void finishTrustedConfirmationOperation(KMOperationState op) {
// Perform trusted confirmation if required
if (op.isTrustedConfirmationRequired()) {
if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) {
KMException.throwIt(KMError.NO_USER_CONFIRMATION);
}
boolean verified =
op.getTrustedConfirmationSigner()
.verify(
KMByteBlob.cast(data[INPUT_DATA]).getBuffer(),
KMByteBlob.cast(data[INPUT_DATA]).getStartOff(),
KMByteBlob.cast(data[INPUT_DATA]).length(),
KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getBuffer(),
KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(),
KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length());
if (!verified) {
KMException.throwIt(KMError.NO_USER_CONFIRMATION);
}
}
}
private boolean isDigestSupported(short alg, short digest) {
switch (alg) {
case KMType.RSA:
case KMType.EC:
if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) {
return false;
}
break;
case KMType.HMAC:
if (digest != KMType.SHA2_256) {
return false;
}
break;
default:
break;
}
return true;
}
}