aboutsummaryrefslogtreecommitdiff
path: root/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java
diff options
context:
space:
mode:
Diffstat (limited to 'ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java')
-rw-r--r--ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java1396
1 files changed, 1396 insertions, 0 deletions
diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java
new file mode 100644
index 0000000..d69534d
--- /dev/null
+++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java
@@ -0,0 +1,1396 @@
+/*
+ * Copyright(C) 2021 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.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.ISO7816;
+import javacard.framework.ISOException;
+import javacard.framework.JCSystem;
+import javacard.framework.Util;
+
+/**
+ * This class handles remote key provisioning. Generates an RKP key and generates a certificate
+ * signing request(CSR). The generation of CSR is divided among multiple functions to the save the
+ * memory inside the Applet. The set of functions to be called sequentially in the order to complete
+ * the process of generating the CSR are processBeginSendData, processUpdateKey,
+ * processUpdateEekChain, processUpdateChallenge, processFinishSendData, and getResponse.
+ * ProcessUpdateKey is called Ntimes, where N is the number of keys. Similarly, getResponse is
+ * called multiple times till the client receives the response completely.
+ */
+public class KMRemotelyProvisionedComponentDevice {
+
+ // Below are the device info labels
+ // The string "brand" in hex
+ public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64};
+ // The string "manufacturer" in hex
+ public static final byte[] MANUFACTURER = {
+ 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72
+ };
+ // The string "product" in hex
+ public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74};
+ // The string "model" in hex
+ public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C};
+ // // The string "device" in hex
+ public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65};
+ // The string "vb_state" in hex
+ public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65};
+ // The string "bootloader_state" in hex.
+ public static final byte[] BOOTLOADER_STATE = {
+ 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65
+ };
+ // The string "vb_meta_digest" in hdex.
+ public static final byte[] VB_META_DIGEST = {
+ 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74
+ };
+ // The string "os_version" in hex.
+ public static final byte[] OS_VERSION = {
+ 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E
+ };
+ // The string "system_patch_level" in hex.
+ public static final byte[] SYSTEM_PATCH_LEVEL = {
+ 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76,
+ 0x65, 0x6C
+ };
+ // The string "boot_patch_level" in hex.
+ public static final byte[] BOOT_PATCH_LEVEL = {
+ 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C
+ };
+ // The string "vendor_patch_level" in hex.
+ public static final byte[] VENDOR_PATCH_LEVEL = {
+ 0x76, 0x65, 0x6E, 0x64, 0x6F, 0x72, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76,
+ 0x65, 0x6C
+ };
+ // The string "version" in hex.
+ public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E};
+ // The string "security_level" in hex.
+ public static final byte[] SECURITY_LEVEL = {
+ 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C
+ };
+ // The string "fused" in hex.
+ public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64};
+ // The string "cert_type" in hex
+ public static final byte[] CERT_TYPE = {0x63, 0x65, 0x72, 0x74, 0x5F, 0x74, 0x79, 0x70, 0x65};
+ // Below are the Verified boot state values
+ // The string "green" in hex.
+ public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E};
+ // The string "yellow" in hex.
+ public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77};
+ // The string "orange" in hex.
+ public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65};
+ // The string "red" in hex.
+ public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64};
+ // Below are the boot loader state values
+ // The string "unlocked" in hex.
+ public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64};
+ // The string "locked" in hex.
+ public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64};
+ // Device info CDDL schema version
+ public static final byte DI_SCHEMA_VERSION = 2;
+ // The string "strongbox" in hex.
+ public static final byte[] DI_SECURITY_LEVEL = {
+ 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78
+ };
+ // The string "keymint" in hex.
+ public static final byte[] DI_CERT_TYPE = {0x6B, 0x65, 0x79, 0x6D, 0x69, 0x6E, 0x74};
+ // Represents each element size inside the data buffer. Each element has two entries
+ // 1) Length of the element and 2) offset of the element in the data buffer.
+ public static final byte DATA_INDEX_ENTRY_SIZE = 4;
+ // It is the offset, which represents the position where the element is present
+ // in the data buffer.
+ public static final byte DATA_INDEX_ENTRY_OFFSET = 2;
+ // Flag to denote TRUE
+ private static final byte TRUE = 0x01;
+ // Flag to denote FALSE
+ private static final byte FALSE = 0x00;
+ // RKP hardware info Version
+ private static final byte RKP_VERSION = 0x03;
+ // RKP supportedNumKeysInCsr
+ private static final byte MIN_SUPPORTED_NUM_KEYS_IN_CSR = 20;
+ // The CsrPayload CDDL Schema version.
+ private static final byte CSR_PAYLOAD_CDDL_SCHEMA_VERSION = 3;
+ // Boot params
+ // Below constants used to denote the type of the boot parameters. Note that these
+ // constants are only defined to be used internally.
+ private static final byte OS_VERSION_ID = 0x00;
+ private static final byte SYSTEM_PATCH_LEVEL_ID = 0x01;
+ private static final byte BOOT_PATCH_LEVEL_ID = 0x02;
+ private static final byte VENDOR_PATCH_LEVEL_ID = 0x03;
+ // Configurable flag to denote if UDS certificate chain is supported in the
+ // RKP server.
+ private static final boolean IS_UDS_SUPPORTED_IN_RKP_SERVER = true;
+ // Denotes COSE Integer lengths less than or equal to 23.
+ private static final byte TINY_PAYLOAD = 0x17;
+ // Denotes COSE Integer with short lengths.
+ private static final short SHORT_PAYLOAD = 0x100;
+ // The maximum possible output buffer.
+ private static final short MAX_SEND_DATA = 512;
+ // The string "Google Strongbox KeyMint 3" in hex.
+ private static final byte[] uniqueId = {
+ 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78,
+ 0x20, 0x4b, 0x65, 0x79, 0x4d, 0x69, 0x6e, 0x74, 0x20, 0x33
+ };
+ // Flag to denote more response is available to the clients.
+ private static final byte MORE_DATA = 0x01;
+ // Flag to denote no response is available to the clients.
+ private static final byte NO_DATA = 0x00;
+ // Below are the response processing states.
+ private static final byte START_PROCESSING = 0x00;
+ private static final byte PROCESSING_DICE_CERTS_IN_PROGRESS = 0x02;
+ private static final byte PROCESSING_DICE_CERTS_COMPLETE = 0x04;
+ private static final byte PROCESSING_UDS_CERTS_IN_PROGRESS = 0x08;
+ private static final byte PROCESSING_UDS_CERTS_COMPLETE = 0x0A;
+ // The data table size.
+ private static final short DATA_SIZE = 512;
+ // Number of entries in the data table.
+ private static final byte DATA_INDEX_SIZE = 6;
+ // Below are the data table offsets.
+ private static final byte TOTAL_KEYS_TO_SIGN = 0;
+ private static final byte KEYS_TO_SIGN_COUNT = 1;
+ private static final byte GENERATE_CSR_PHASE = 2;
+ private static final byte RESPONSE_PROCESSING_STATE = 3;
+ private static final byte UDS_PROCESSED_LENGTH = 4;
+ private static final byte DICE_PROCESSED_LENGTH = 5;
+
+ // Below are some of the sizes defined in the data table.
+ // The size of the Ephemeral Mac key used to sign the rkp public key.
+ private static final byte EPHEMERAL_MAC_KEY_SIZE = 32;
+ // The size of short types.
+ private static final byte SHORT_SIZE = 2;
+ // The size of byte types
+ private static final byte BYTE_SIZE = 1;
+ // Below are the different processing stages for generateCSR.
+ // BEGIN - It is the initial stage where the process is initialized and construction of
+ // MacedPublickeys are initiated.
+ // UPDATE - Challenge, EEK and RKP keys are sent to the applet for further process.
+ // FINISH - MacedPublicKeys are constructed and construction of protected data is initiated.
+ // GET_UDS_CERTS_RESPONSE - Constructed the UDSCerts in the response.
+ private static final byte BEGIN = 0x01;
+ private static final byte UPDATE = 0x02;
+ private static final byte FINISH = 0x04;
+ private static final byte GET_UDS_CERTS_RESPONSE = 0x06;
+
+ // RKP mac key size
+ private static final byte RKP_MAC_KEY_SIZE = 32;
+ // RKP CDDL Schema version
+ private static final byte RKP_AUTHENTICATE_CDDL_SCHEMA_VERSION = 1;
+ // The maximum size of the encoded buffer size.
+ private static final short MAX_ENCODED_BUF_SIZE = 1024;
+ // Used to hold the temporary results.
+ public short[] rkpTmpVariables;
+ // Data table to hold the entries at the initial stages of generateCSR and which are used
+ // at later stages to construct the response data.
+ private byte[] data;
+ // Instance of the CBOR encoder.
+ private KMEncoder encoder;
+ // Instance of the CBOR decoder.
+ private KMDecoder decoder;
+ // Instance of the KMRepository for memory management.
+ private KMRepository repository;
+ // Instance of the provider for cyrpto operations.
+ private KMSEProvider seProvider;
+ // Instance of the KMKeymintDataStore to save or retrieve the data.
+ private KMKeymintDataStore storeDataInst;
+ // Holds the KMOperation instance. This is used to do multi part update operations.
+ private Object[] operation;
+ // Holds the current index in the data table.
+ private short[] dataIndex;
+
+ public KMRemotelyProvisionedComponentDevice(
+ KMEncoder encoder,
+ KMDecoder decoder,
+ KMRepository repository,
+ KMSEProvider seProvider,
+ KMKeymintDataStore storeDInst) {
+ this.encoder = encoder;
+ this.decoder = decoder;
+ this.repository = repository;
+ this.seProvider = seProvider;
+ this.storeDataInst = storeDInst;
+ rkpTmpVariables = JCSystem.makeTransientShortArray((short) 32, JCSystem.CLEAR_ON_RESET);
+ data = JCSystem.makeTransientByteArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET);
+ operation = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET);
+ dataIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET);
+ // Initialize RKP mac key
+ if (!seProvider.isUpgrading()) {
+ short offset = repository.allocReclaimableMemory((short) RKP_MAC_KEY_SIZE);
+ byte[] buffer = repository.getHeap();
+ seProvider.getTrueRandomNumber(buffer, offset, RKP_MAC_KEY_SIZE);
+ storeDataInst.createRkpMacKey(buffer, offset, RKP_MAC_KEY_SIZE);
+ repository.reclaimMemory(RKP_MAC_KEY_SIZE);
+ }
+ operation[0] = null;
+ }
+
+ private void initializeDataTable() {
+ clearDataTable();
+ releaseOperation();
+ dataIndex[0] = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE);
+ }
+
+ private short dataAlloc(short length) {
+ if ((short) (dataIndex[0] + length) > (short) data.length) {
+ ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
+ }
+ dataIndex[0] += length;
+ return (short) (dataIndex[0] - length);
+ }
+
+ private void clearDataTable() {
+ Util.arrayFillNonAtomic(data, (short) 0, (short) data.length, (byte) 0x00);
+ dataIndex[0] = 0x00;
+ }
+
+ private void releaseOperation() {
+ if (operation[0] != null) {
+ ((KMOperation) operation[0]).abort();
+ operation[0] = null;
+ }
+ }
+
+ private short createEntry(short index, short length) {
+ index = (short) (index * DATA_INDEX_ENTRY_SIZE);
+ short ptr = dataAlloc(length);
+ Util.setShort(data, index, length);
+ Util.setShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET), ptr);
+ return ptr;
+ }
+
+ private short getEntry(short index) {
+ index = (short) (index * DATA_INDEX_ENTRY_SIZE);
+ return Util.getShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET));
+ }
+
+ private void processGetRkpHwInfoCmd(APDU apdu) {
+ // Make the response
+ // Author name - Google.
+ short respPtr = KMArray.instance((short) 6);
+ KMArray resp = KMArray.cast(respPtr);
+ resp.add((short) 0, KMInteger.uint_16(KMError.OK));
+ resp.add((short) 1, KMInteger.uint_16(RKP_VERSION));
+ resp.add(
+ (short) 2,
+ KMByteBlob.instance(
+ KMKeymasterApplet.Google, (short) 0, (short) KMKeymasterApplet.Google.length));
+ // This field is no longer used in version 3
+ resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_NONE));
+ resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length));
+ resp.add((short) 5, KMInteger.uint_16(MIN_SUPPORTED_NUM_KEYS_IN_CSR));
+ KMKeymasterApplet.sendOutgoing(apdu, respPtr);
+ }
+
+ /**
+ * This function generates an EC key pair with attest key as purpose and creates an encrypted key
+ * blob. It then generates a COSEMac message which includes the ECDSA public key.
+ */
+ public void processGenerateRkpKey(APDU apdu) {
+ short arr = KMArray.instance((short) 1);
+ KMArray.cast(arr).add((short) 0, KMSimpleValue.exp());
+ arr = KMKeymasterApplet.receiveIncoming(apdu, arr);
+ // Re-purpose the apdu buffer as scratch pad.
+ byte[] scratchPad = apdu.getBuffer();
+ // test mode flag.
+ boolean testMode =
+ (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 0)).getValue());
+ KMKeymasterApplet.generateRkpKey(scratchPad, getEcAttestKeyParameters());
+ short pubKey = KMKeymasterApplet.getPubKey();
+ short coseMac0 = constructCoseMacForRkpKey(testMode, scratchPad, pubKey);
+ // Encode the COSE_MAC0 object
+ arr = KMArray.instance((short) 3);
+ KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(arr).add((short) 1, coseMac0);
+ KMArray.cast(arr).add((short) 2, KMKeymasterApplet.getPivateKey());
+ KMKeymasterApplet.sendOutgoing(apdu, arr);
+ }
+
+ public short getHeaderLen(short length) {
+ if (length <= TINY_PAYLOAD) {
+ return (short) 1;
+ } else if (length < SHORT_PAYLOAD) {
+ return (short) 2;
+ } else {
+ return (short) 3;
+ }
+ }
+
+ public void constructPartialSignedData(
+ byte[] scratchPad,
+ short coseKeysCount,
+ short totalCoseKeysLen,
+ short challengeByteBlob,
+ short deviceInfo,
+ short versionPtr,
+ short certTypePtr) {
+ // Initialize ECDSA operation
+ initECDSAOperation();
+
+ short versionLength = encoder.getEncodedLength(versionPtr);
+ short certTypeLen = encoder.getEncodedLength(certTypePtr);
+ short challengeLen = (short) KMByteBlob.cast(challengeByteBlob).length();
+ if (challengeLen < 16 || challengeLen > 64) {
+ KMException.throwIt(KMError.INVALID_INPUT_LENGTH);
+ }
+ short challengeHeaderLen = encoder.getEncodedBytesLength(challengeLen);
+ short deviceInfoLen = encoder.getEncodedLength(deviceInfo);
+
+ // Calculate the keysToSign length
+ // keysToSignLen = coseKeysArrayHeaderLen + totalCoseKeysLen
+ short coseKeysArrHeaderLen = getHeaderLen(coseKeysCount);
+ short keysToSignLen = (short) (coseKeysArrHeaderLen + totalCoseKeysLen);
+
+ // Calculate the payload array header len
+ /*
+ * paylaodArrHeaderLen is Array of 2 elements that occupies 1 byte.
+ * SignedData = [challenge, AuthenticatedRequest<CsrPayload>]
+ */
+ short paylaodArrHeaderLen = 1;
+ /*
+ * csrPaylaodArrHeaderLen is Array of 4 elements that occupies 1 byte.
+ * CsrPayload = [version: 3, CertificateType, DeviceInfo, KeysToSign]
+ */
+ short csrPaylaodArrHeaderLen = 1;
+ short csrPayloadLen =
+ (short)
+ (csrPaylaodArrHeaderLen + versionLength + certTypeLen + deviceInfoLen + keysToSignLen);
+ short csrPaylaodByteHeaderLen = encoder.getEncodedBytesLength(csrPayloadLen);
+ short payloadLen =
+ (short)
+ (paylaodArrHeaderLen
+ + challengeHeaderLen
+ + challengeLen
+ + csrPaylaodByteHeaderLen
+ + csrPaylaodArrHeaderLen
+ + versionLength
+ + certTypeLen
+ + deviceInfoLen
+ + keysToSignLen);
+
+ // Empty aad
+ short aad = KMByteBlob.instance(scratchPad, (short) 0, (short) 0);
+
+ /* construct protected header */
+ short protectedHeaders =
+ KMCose.constructHeaders(
+ rkpTmpVariables,
+ KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE);
+ protectedHeaders =
+ KMKeymasterApplet.encodeToApduBuffer(
+ protectedHeaders, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders);
+
+ // Construct partial signature
+ short signStructure =
+ KMCose.constructCoseSignStructure(protectedHeaders, aad, KMType.INVALID_VALUE);
+ short partialSignStructureLen =
+ KMKeymasterApplet.encodeToApduBuffer(
+ signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ ((KMOperation) operation[0]).update(scratchPad, (short) 0, partialSignStructureLen);
+
+ // Add payload Byte Header
+ short prevReclaimIndex = repository.getHeapReclaimIndex();
+ byte[] heap = repository.getHeap();
+ short heapIndex = repository.allocReclaimableMemory(MAX_ENCODED_BUF_SIZE);
+ short byteBlobHeaderLen = encoder.encodeByteBlobHeader(payloadLen, heap, heapIndex, (short) 3);
+ ((KMOperation) operation[0]).update(heap, heapIndex, byteBlobHeaderLen);
+
+ short arr = KMArray.instance((short) 2);
+ KMArray.cast(arr).add((short) 0, challengeByteBlob);
+ KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE);
+ short payloadArrayLen = encoder.encode(arr, heap, heapIndex, prevReclaimIndex);
+ ((KMOperation) operation[0]).update(heap, heapIndex, payloadArrayLen);
+
+ byteBlobHeaderLen = encoder.encodeByteBlobHeader(csrPayloadLen, heap, heapIndex, (short) 3);
+ ((KMOperation) operation[0]).update(heap, heapIndex, byteBlobHeaderLen);
+
+ // Construct partial csr payload array
+ arr = KMArray.instance((short) 4);
+ KMArray.cast(arr).add((short) 0, versionPtr);
+ KMArray.cast(arr).add((short) 1, certTypePtr);
+ KMArray.cast(arr).add((short) 2, deviceInfo);
+ KMArray.cast(arr).add((short) 3, KMType.INVALID_VALUE);
+ short partialCsrPayloadArrayLen = encoder.encode(arr, heap, heapIndex, prevReclaimIndex);
+ ((KMOperation) operation[0]).update(heap, heapIndex, partialCsrPayloadArrayLen);
+
+ // Encode keysToSign Array Header length
+ short keysToSignArrayHeaderLen =
+ encoder.encodeArrayHeader(coseKeysCount, heap, heapIndex, (short) 3);
+ ((KMOperation) operation[0]).update(heap, heapIndex, keysToSignArrayHeaderLen);
+ repository.reclaimMemory(MAX_ENCODED_BUF_SIZE);
+ }
+
+ /**
+ * This is the first command of the generateCSR.
+ * Input:
+ * 1) Number of RKP keys.
+ * 2) Total size of the encoded CoseKeys (Each RKP key is represented in CoseKey)
+ * 3) Challenge
+ * Process:
+ * 1) creates device info, initializes version and cert type.
+ * 2) Initialize the ECDSA operation with the deviceUniqueKeyPair and do partial sign of the
+ * CsrPayload with the initial input data received. A Multipart update on ECDSA is
+ * called on each updateKey command in the second stage.
+ * 3) Store the number of RKP keys in the temporary data buffer.
+ * 4) Update the phase of the generateCSR function to BEGIN.
+ * 5) generates RKP device info
+ * Response:
+ * 1) Send OK response.
+ * 2) Encoded device info
+ * 3) CSR payload CDDL schema version
+ * 4) Cert type
+ *
+ * @param apdu Input apdu
+ */
+ public void processBeginSendData(APDU apdu) throws Exception {
+ try {
+ initializeDataTable();
+ short arr = KMArray.instance((short) 3);
+ KMArray.cast(arr).add((short) 0, KMInteger.exp()); // Array length
+ KMArray.cast(arr).add((short) 1, KMInteger.exp()); // Total length of the encoded CoseKeys.
+ KMArray.cast(arr).add((short) 2, KMByteBlob.exp()); // challenge
+ arr = KMKeymasterApplet.receiveIncoming(apdu, arr);
+ // Re-purpose the apdu buffer as scratch pad.
+ byte[] scratchPad = apdu.getBuffer();
+ short deviceInfo = createDeviceInfo(scratchPad);
+ short versionPtr = KMInteger.uint_16(CSR_PAYLOAD_CDDL_SCHEMA_VERSION);
+ short certTypePtr =
+ KMTextString.instance(DI_CERT_TYPE, (short) 0, (short) DI_CERT_TYPE.length);
+
+ constructPartialSignedData(
+ scratchPad,
+ KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(),
+ KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort(),
+ KMArray.cast(arr).get((short) 2),
+ deviceInfo,
+ versionPtr,
+ certTypePtr);
+ // Store the total keys in data table.
+ short dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE);
+ Util.setShort(
+ data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort());
+ // Store the current csr status, which is BEGIN.
+ createEntry(GENERATE_CSR_PHASE, BYTE_SIZE);
+ updateState(BEGIN);
+ if (0 == KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()) {
+ updateState(UPDATE);
+ }
+ short prevReclaimIndex = repository.getHeapReclaimIndex();
+ short offset = repository.allocReclaimableMemory(MAX_ENCODED_BUF_SIZE);
+ short length =
+ encoder.encode(
+ deviceInfo, repository.getHeap(), offset, prevReclaimIndex, MAX_ENCODED_BUF_SIZE);
+ short encodedDeviceInfo = KMByteBlob.instance(repository.getHeap(), offset, length);
+ // release memory
+ repository.reclaimMemory(MAX_ENCODED_BUF_SIZE);
+ // Send response.
+ short array = KMArray.instance((short) 4);
+ KMArray.cast(array).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(array).add((short) 1, encodedDeviceInfo);
+ KMArray.cast(array).add((short) 2, versionPtr);
+ KMArray.cast(array).add((short) 3, certTypePtr);
+ KMKeymasterApplet.sendOutgoing(apdu, array);
+ } catch (Exception e) {
+ clearDataTable();
+ releaseOperation();
+ throw e;
+ }
+ }
+
+ /**
+ * This is the second command of the generateCSR. This command will be called in a loop
+ * for the number of keys.
+ * Input:
+ * CoseMac0 containing the RKP Key
+ * Process:
+ * 1) Validate the phase of generateCSR. Prior state should be either BEGIN or UPDATE.
+ * 2) Validate the number of RKP Keys received against the value received in the first command.
+ * 3) Validate the CoseMac0 structure and extract the RKP Key.
+ * 4) Do Multipart ECDSA update operation with the input as RKP key.
+ * 5) Update the number of keys received count into the data buffer.
+ * 6) Update the phase of the generateCSR function to UPDATE.
+ * Response:
+ * 1) Send OK response.
+ * 2) encoded Cose Key
+ * @param apdu Input apdu
+ */
+ public void processUpdateKey(APDU apdu) throws Exception {
+ try {
+ // The prior state can be BEGIN or UPDATE
+ validateState((byte) (BEGIN | UPDATE));
+ validateKeysToSignCount();
+ short headers = KMCoseHeaders.exp();
+ short arrInst = KMArray.instance((short) 4);
+ short byteBlobExp = KMByteBlob.exp();
+ KMArray.cast(arrInst).add((short) 0, byteBlobExp);
+ KMArray.cast(arrInst).add((short) 1, headers);
+ KMArray.cast(arrInst).add((short) 2, byteBlobExp);
+ KMArray.cast(arrInst).add((short) 3, byteBlobExp);
+ short arr = KMArray.exp(arrInst);
+ arr = KMKeymasterApplet.receiveIncoming(apdu, arr);
+ arrInst = KMArray.cast(arr).get((short) 0);
+ // Re-purpose the apdu buffer as scratch pad.
+ byte[] scratchPad = apdu.getBuffer();
+
+ // Validate and extract the CoseKey from CoseMac0 message.
+ short coseKey = validateAndExtractPublicKey(arrInst, scratchPad);
+ // Encode CoseKey
+ short length =
+ KMKeymasterApplet.encodeToApduBuffer(
+ coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ // Do ECDSA update with input as encoded CoseKey.
+ ((KMOperation) operation[0]).update(scratchPad, (short) 0, length);
+ short encodedCoseKey = KMByteBlob.instance(scratchPad, (short) 0, length);
+
+ // Increment the count each time this function gets executed.
+ // Store the count in data table.
+ short dataEntryIndex = getEntry(KEYS_TO_SIGN_COUNT);
+ if (dataEntryIndex == 0) {
+ dataEntryIndex = createEntry(KEYS_TO_SIGN_COUNT, SHORT_SIZE);
+ }
+ length = Util.getShort(data, dataEntryIndex);
+ Util.setShort(data, dataEntryIndex, ++length);
+ // Update the csr state
+ updateState(UPDATE);
+ // Send response.
+ short array = KMArray.instance((short) 2);
+ KMArray.cast(array).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(array).add((short) 1, encodedCoseKey);
+ KMKeymasterApplet.sendOutgoing(apdu, array);
+ } catch (Exception e) {
+ clearDataTable();
+ releaseOperation();
+ throw e;
+ }
+ }
+
+ /**
+ * This is the third command of generateCSR.
+ * Input:
+ * No input data.
+ * Process:
+ * 1) Validate the phase of generateCSR. Prior state should be UPDATE.
+ * 2) Check if all the RKP keys are received, if not, throw exception.
+ * 3) Finalize the ECDSA operation and get the signature, signature of SignedDataSigStruct
+ * where SignedDataSigStruct is
+ * [context: "Signature1",
+ * protected: bstr .cbor {1 : AlgorithmEdDSA / AlgorithmES256},
+ * external_aad: bstr .size 0,
+ * payload: bstr .cbor [challenge,
+ * bstr .cbor CsrPayload = [version, CertificateType, DeviceInfo, KeysToSign]
+ * ]
+ * ]
+ * 4) Construct protected header data.
+ * 5) Update the phase of the generateCSR function to FINISH.
+ * Response:
+ * OK
+ * protectedHeader - SignedData protected header
+ * Signature of SignedDataSigStruct
+ * Version - The AuthenticatedRequest CDDL Schema version.
+ * Flag to represent there is more data to retrieve.
+ * @param apdu Input apdu.
+ */
+ public void processFinishSendData(APDU apdu) throws Exception {
+ try {
+ // The prior state should be UPDATE.
+ validateState(UPDATE);
+ byte[] scratchPad = apdu.getBuffer();
+ if (data[getEntry(TOTAL_KEYS_TO_SIGN)] != data[getEntry(KEYS_TO_SIGN_COUNT)]) {
+ // Mismatch in the number of keys sent.
+ ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
+ }
+ // PubKeysToSignMac
+ short empty = repository.alloc((short) 0);
+ short len =
+ ((KMOperation) operation[0])
+ .sign(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0);
+ releaseOperation();
+ short signatureData = KMByteBlob.instance(scratchPad, (short) 0, len);
+ len = KMAsn1Parser.instance().decodeEcdsa256Signature(signatureData, scratchPad, (short) 0);
+
+ signatureData = KMByteBlob.instance(scratchPad, (short) 0, len);
+
+ /* construct protected header */
+ short protectedHeaders =
+ KMCose.constructHeaders(
+ rkpTmpVariables,
+ KMNInteger.uint_8(KMCose.COSE_ALG_ES256),
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE);
+ protectedHeaders =
+ KMKeymasterApplet.encodeToApduBuffer(
+ protectedHeaders, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders);
+
+ updateState(FINISH);
+ short arr = KMArray.instance((short) 5);
+ KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(arr).add((short) 1, protectedHeaders);
+ KMArray.cast(arr).add((short) 2, signatureData);
+ KMArray.cast(arr).add((short) 3, KMInteger.uint_8(RKP_AUTHENTICATE_CDDL_SCHEMA_VERSION));
+ KMArray.cast(arr).add((short) 4, KMInteger.uint_8(MORE_DATA));
+ KMKeymasterApplet.sendOutgoing(apdu, arr);
+ } catch (Exception e) {
+ clearDataTable();
+ releaseOperation();
+ throw e;
+ }
+ }
+
+ /**
+ * This is the fourth command of generateCSR. This command is called multiple times by the
+ * HAL until complete UdsCerts are received. On each call, a chunk of 512 bytes data is sent.
+ * Input:
+ * No input data.
+ * Process:
+ * 1) Validate the phase of generateCSR. Prior state should be FINISH.
+ * 2) checks if Uds cert is present and sends the certs in chunks of 512 bytes.
+ * 3) Update the phase of the generateCSR function to GET_UDS_CERTS_RESPONSE. In-case of
+ * a) No Uds certs present and
+ * b) When last chunk of Uds cert is sent
+ * Response:
+ * OK
+ * Uds cert data
+ * Flag to represent there is more data to retrieve.
+ * @param apdu Input apdu.
+ */
+ public void processGetUdsCerts(APDU apdu) throws Exception {
+ try {
+ // The prior state should be FINISH.
+ validateState((byte) (FINISH));
+ short len;
+ byte moreData;
+ byte[] scratchPad = apdu.getBuffer();
+ if (!isUdsCertsChainPresent()) {
+ createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE);
+ updateState(GET_UDS_CERTS_RESPONSE);
+ moreData = NO_DATA;
+ scratchPad[0] = (byte) 0xA0; // CBOR Encoded empty map is A0
+ len = 1;
+ } else {
+ len = processUdsCertificateChain(scratchPad);
+ moreData = MORE_DATA;
+ byte state = getCurrentOutputProcessingState();
+ switch (state) {
+ case PROCESSING_UDS_CERTS_IN_PROGRESS:
+ moreData = MORE_DATA;
+ break;
+ case PROCESSING_UDS_CERTS_COMPLETE:
+ updateState(GET_UDS_CERTS_RESPONSE);
+ moreData = NO_DATA;
+ break;
+ default:
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ }
+ short data = KMByteBlob.instance(scratchPad, (short) 0, len);
+ short arr = KMArray.instance((short) 3);
+ KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(arr).add((short) 1, data);
+ // represents there is more output to retrieve
+ KMArray.cast(arr).add((short) 2, KMInteger.uint_8(moreData));
+ KMKeymasterApplet.sendOutgoing(apdu, arr);
+ } catch (Exception e) {
+ clearDataTable();
+ throw e;
+ }
+ }
+
+ /**
+ * This is the fifth command of generateCSR. This command is called multiple times by the
+ * HAL until complete Dice cert chain is received. On each call, a chunk of 512 bytes data is sent.
+ * Input:
+ * No input data.
+ * Process:
+ * 1) Validate the phase of generateCSR. Prior state should be GET_UDS_CERTS_RESPONSE.
+ * 2) Sends the Dice cert chain data in chunks of 512 bytes.
+ *
+ * After receiving a complete dice cert chain in HAL, Hal constructs the final CSR using the output data
+ * returned from all the 5 generateCSR commands in Applet.
+ *
+ * Response:
+ * OK
+ * Dice cert chain data
+ * Flag to represent there is more data to retrieve.
+ * @param apdu Input apdu.
+ */
+ public void processGetDiceCertChain(APDU apdu) throws Exception {
+ try {
+ // The prior state should be GET_UDS_CERTS_RESPONSE.
+ validateState((byte) (GET_UDS_CERTS_RESPONSE));
+ byte[] scratchPad = apdu.getBuffer();
+ short len = 0;
+ len = processDiceCertChain(scratchPad);
+ byte moreData = MORE_DATA;
+ byte state = getCurrentOutputProcessingState();
+ switch (state) {
+ case PROCESSING_DICE_CERTS_IN_PROGRESS:
+ moreData = MORE_DATA;
+ break;
+ case PROCESSING_DICE_CERTS_COMPLETE:
+ moreData = NO_DATA;
+ clearDataTable();
+ break;
+ default:
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ short data = KMByteBlob.instance(scratchPad, (short) 0, len);
+ short arr = KMArray.instance((short) 3);
+ KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK));
+ KMArray.cast(arr).add((short) 1, data);
+ // represents there is more output to retrieve
+ KMArray.cast(arr).add((short) 2, KMInteger.uint_8(moreData));
+ KMKeymasterApplet.sendOutgoing(apdu, arr);
+ } catch (Exception e) {
+ clearDataTable();
+ throw e;
+ }
+ }
+
+ private boolean isUdsCertsChainPresent() {
+ if (!IS_UDS_SUPPORTED_IN_RKP_SERVER || (storeDataInst.getUdsCertChainLength() == 0)) {
+ return false;
+ }
+ return true;
+ }
+
+ public void process(short ins, APDU apdu) throws Exception {
+ switch (ins) {
+ case KMKeymasterApplet.INS_GET_RKP_HARDWARE_INFO:
+ processGetRkpHwInfoCmd(apdu);
+ break;
+ case KMKeymasterApplet.INS_GENERATE_RKP_KEY_CMD:
+ processGenerateRkpKey(apdu);
+ break;
+ case KMKeymasterApplet.INS_BEGIN_SEND_DATA_CMD:
+ processBeginSendData(apdu);
+ break;
+ case KMKeymasterApplet.INS_UPDATE_KEY_CMD:
+ processUpdateKey(apdu);
+ break;
+ case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD:
+ processFinishSendData(apdu);
+ break;
+ case KMKeymasterApplet.INS_GET_UDS_CERTS_CMD:
+ processGetUdsCerts(apdu);
+ break;
+ case KMKeymasterApplet.INS_GET_DICE_CERT_CHAIN_CMD:
+ processGetDiceCertChain(apdu);
+ break;
+ default:
+ ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
+ }
+ }
+
+ private byte getCurrentOutputProcessingState() {
+ short index = getEntry(RESPONSE_PROCESSING_STATE);
+ if (index == 0) {
+ return START_PROCESSING;
+ }
+ return data[index];
+ }
+
+ private void updateOutputProcessingState(byte state) {
+ short dataEntryIndex = getEntry(RESPONSE_PROCESSING_STATE);
+ data[dataEntryIndex] = state;
+ }
+
+ /**
+ * Validates the CoseMac message and extracts the CoseKey from it.
+ *
+ * @param coseMacPtr CoseMac instance to be validated.
+ * @param scratchPad Scratch buffer used to store temp results.
+ * @return CoseKey instance.
+ */
+ private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) {
+ // Version 3 removes the need to have testMode in function calls
+ boolean testMode = false;
+ // Exp for KMCoseHeaders
+ short coseHeadersExp = KMCoseHeaders.exp();
+ // Exp for coseky
+ short coseKeyExp = KMCoseKey.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(rkpTmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) {
+ KMException.throwIt(KMError.STATUS_FAILED);
+ }
+
+ // Validate payload.
+ ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET);
+ ptr =
+ decoder.decode(
+ coseKeyExp,
+ KMByteBlob.cast(ptr).getBuffer(),
+ KMByteBlob.cast(ptr).getStartOff(),
+ KMByteBlob.cast(ptr).length());
+
+ if (!KMCoseKey.cast(ptr)
+ .isDataValid(
+ rkpTmpVariables,
+ KMCose.COSE_KEY_TYPE_EC2,
+ KMType.INVALID_VALUE,
+ KMCose.COSE_ALG_ES256,
+ KMCose.COSE_ECCURVE_256)) {
+ KMException.throwIt(KMError.STATUS_FAILED);
+ }
+
+ boolean isTestKey = KMCoseKey.cast(ptr).isTestKey();
+ if (isTestKey && !testMode) {
+ KMException.throwIt(KMError.STATUS_TEST_KEY_IN_PRODUCTION_REQUEST);
+ } else if (!isTestKey && testMode) {
+ KMException.throwIt(KMError.STATUS_PRODUCTION_KEY_IN_TEST_REQUEST);
+ }
+
+ // Compute CoseMac Structure and compare the macs.
+ short macStructure =
+ KMCose.constructCoseMacStructure(
+ KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET),
+ KMByteBlob.instance((short) 0),
+ KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET));
+ short encodedLen =
+ KMKeymasterApplet.encodeToApduBuffer(
+ macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+
+ short hmacLen =
+ rkpHmacSign(testMode, scratchPad, (short) 0, encodedLen, scratchPad, encodedLen);
+
+ if (hmacLen
+ != KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length()) {
+ KMException.throwIt(KMError.STATUS_INVALID_MAC);
+ }
+
+ if (0
+ != Util.arrayCompare(
+ scratchPad,
+ 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(),
+ hmacLen)) {
+ KMException.throwIt(KMError.STATUS_INVALID_MAC);
+ }
+ return ptr;
+ }
+
+ private void validateKeysToSignCount() {
+ short index = getEntry(KEYS_TO_SIGN_COUNT);
+ short keysToSignCount = 0;
+ if (index != 0) {
+ keysToSignCount = Util.getShort(data, index);
+ }
+ if (Util.getShort(data, getEntry(TOTAL_KEYS_TO_SIGN)) <= keysToSignCount) {
+ // Mismatch in the number of keys sent.
+ ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
+ }
+ }
+
+ private void validateState(byte expectedState) {
+ short dataEntryIndex = getEntry(GENERATE_CSR_PHASE);
+ if (0 == (data[dataEntryIndex] & expectedState)) {
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ }
+
+ private void updateState(byte state) {
+ short dataEntryIndex = getEntry(GENERATE_CSR_PHASE);
+ if (dataEntryIndex == 0) {
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ data[dataEntryIndex] = state;
+ }
+
+ /*
+ * Create DeviceInfo structure as specified in the RKPV3.0 specification.
+ */
+ private short createDeviceInfo(byte[] scratchpad) {
+ // Device Info Key Value pairs.
+ for (short i = 0; i < 32; i++) {
+ rkpTmpVariables[i] = KMType.INVALID_VALUE;
+ }
+ short dataOffset = 2;
+ rkpTmpVariables[0] = dataOffset;
+ rkpTmpVariables[1] = 0;
+ short metaOffset = 0;
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ BRAND,
+ getAttestationId(KMType.ATTESTATION_ID_BRAND, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ MANUFACTURER,
+ getAttestationId(KMType.ATTESTATION_ID_MANUFACTURER, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ PRODUCT,
+ getAttestationId(KMType.ATTESTATION_ID_PRODUCT, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ MODEL,
+ getAttestationId(KMType.ATTESTATION_ID_MODEL, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ DEVICE,
+ getAttestationId(KMType.ATTESTATION_ID_DEVICE, scratchpad));
+ updateItem(rkpTmpVariables, metaOffset, VB_STATE, getVbState());
+ updateItem(rkpTmpVariables, metaOffset, BOOTLOADER_STATE, getBootloaderState());
+ updateItem(rkpTmpVariables, metaOffset, VB_META_DIGEST, getVerifiedBootHash(scratchpad));
+ updateItem(rkpTmpVariables, metaOffset, OS_VERSION, getBootParams(OS_VERSION_ID, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ SYSTEM_PATCH_LEVEL,
+ getBootParams(SYSTEM_PATCH_LEVEL_ID, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ BOOT_PATCH_LEVEL,
+ getBootParams(BOOT_PATCH_LEVEL_ID, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ VENDOR_PATCH_LEVEL,
+ getBootParams(VENDOR_PATCH_LEVEL_ID, scratchpad));
+ updateItem(
+ rkpTmpVariables,
+ metaOffset,
+ SECURITY_LEVEL,
+ KMTextString.instance(DI_SECURITY_LEVEL, (short) 0, (short) DI_SECURITY_LEVEL.length));
+ updateItem(rkpTmpVariables, metaOffset, FUSED, KMInteger.uint_8(storeDataInst.secureBootMode));
+ // Create device info map.
+ short map = KMMap.instance(rkpTmpVariables[1]);
+ short mapIndex = 0;
+ short index = 2;
+ while (index < (short) 32) {
+ if (rkpTmpVariables[index] != KMType.INVALID_VALUE) {
+ KMMap.cast(map)
+ .add(mapIndex++, rkpTmpVariables[index], rkpTmpVariables[(short) (index + 1)]);
+ }
+ index += 2;
+ }
+ KMMap.cast(map).canonicalize();
+ return map;
+ }
+
+ // Below 6 methods are helper methods to create device info structure.
+ // ----------------------------------------------------------------------------
+
+ /**
+ * Update the item inside the device info structure.
+ *
+ * @param deviceIds Device Info structure to be updated.
+ * @param metaOffset Out parameter meta information. Offset 0 is index and Offset 1 is length.
+ * @param item Key info to be updated.
+ * @param value value to be updated.
+ */
+ private void updateItem(short[] deviceIds, short metaOffset, byte[] item, short value) {
+ if (KMType.INVALID_VALUE != value) {
+ deviceIds[deviceIds[metaOffset]++] =
+ KMTextString.instance(item, (short) 0, (short) item.length);
+ deviceIds[deviceIds[metaOffset]++] = value;
+ deviceIds[(short) (metaOffset + 1)]++;
+ }
+ }
+
+ private short getAttestationId(short attestId, byte[] scratchpad) {
+ short attIdTagLen = storeDataInst.getAttestationId(attestId, scratchpad, (short) 0);
+ if (attIdTagLen == 0) {
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ return KMTextString.instance(scratchpad, (short) 0, attIdTagLen);
+ }
+
+ private short getVerifiedBootHash(byte[] scratchPad) {
+ short len = storeDataInst.getVerifiedBootHash(scratchPad, (short) 0);
+ if (len == 0) {
+ KMException.throwIt(KMError.INVALID_STATE);
+ }
+ return KMByteBlob.instance(scratchPad, (short) 0, len);
+ }
+
+ private short getBootloaderState() {
+ short bootloaderState;
+ if (storeDataInst.isDeviceBootLocked()) {
+ bootloaderState = KMTextString.instance(LOCKED, (short) 0, (short) LOCKED.length);
+ } else {
+ bootloaderState = KMTextString.instance(UNLOCKED, (short) 0, (short) UNLOCKED.length);
+ }
+ return bootloaderState;
+ }
+
+ private short getVbState() {
+ short state = storeDataInst.getBootState();
+ short vbState = KMType.INVALID_VALUE;
+ if (state == KMType.VERIFIED_BOOT) {
+ vbState = KMTextString.instance(VB_STATE_GREEN, (short) 0, (short) VB_STATE_GREEN.length);
+ } else if (state == KMType.SELF_SIGNED_BOOT) {
+ vbState = KMTextString.instance(VB_STATE_YELLOW, (short) 0, (short) VB_STATE_YELLOW.length);
+ } else if (state == KMType.UNVERIFIED_BOOT) {
+ vbState = KMTextString.instance(VB_STATE_ORANGE, (short) 0, (short) VB_STATE_ORANGE.length);
+ } else if (state == KMType.FAILED_BOOT) {
+ vbState = KMTextString.instance(VB_STATE_RED, (short) 0, (short) VB_STATE_RED.length);
+ }
+ return vbState;
+ }
+
+ private short converIntegerToTextString(short intPtr, byte[] scratchPad) {
+ // Prepare Hex Values
+ short index = 1;
+ scratchPad[0] = 0x30; // Ascii 0
+ while (index < 10) {
+ scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1);
+ index++;
+ }
+ scratchPad[index++] = 0x41; // Ascii 'A'
+ while (index < 16) {
+ scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1);
+ index++;
+ }
+
+ short intLen = KMInteger.cast(intPtr).length();
+ short intOffset = KMInteger.cast(intPtr).getStartOff();
+ byte[] buf = repository.getHeap();
+ short tsPtr = KMTextString.instance((short) (intLen * 2));
+ short tsStartOff = KMTextString.cast(tsPtr).getStartOff();
+ index = 0;
+ byte nibble;
+ while (index < intLen) {
+ nibble = (byte) ((byte) (buf[intOffset] >> 4) & (byte) 0x0F);
+ buf[tsStartOff] = scratchPad[nibble];
+ nibble = (byte) (buf[intOffset] & 0x0F);
+ buf[(short) (tsStartOff + 1)] = scratchPad[nibble];
+ index++;
+ intOffset++;
+ tsStartOff += 2;
+ }
+ return tsPtr;
+ }
+
+ private short getBootParams(byte bootParam, byte[] scratchPad) {
+ short value = KMType.INVALID_VALUE;
+ switch (bootParam) {
+ case OS_VERSION_ID:
+ value = storeDataInst.getOsVersion();
+ break;
+ case SYSTEM_PATCH_LEVEL_ID:
+ value = storeDataInst.getOsPatch();
+ break;
+ case BOOT_PATCH_LEVEL_ID:
+ value = storeDataInst.getBootPatchLevel();
+ break;
+ case VENDOR_PATCH_LEVEL_ID:
+ value = storeDataInst.getVendorPatchLevel();
+ break;
+ default:
+ KMException.throwIt(KMError.INVALID_ARGUMENT);
+ }
+ // Convert Integer to Text String for OS_VERSION.
+ if (bootParam == OS_VERSION_ID) {
+ value = converIntegerToTextString(value, scratchPad);
+ }
+ return value;
+ }
+ // ----------------------------------------------------------------------------
+
+ // ----------------------------------------------------------------------------
+ private void initECDSAOperation() {
+ KMKey deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair();
+ operation[0] =
+ seProvider.getRkpOperation(
+ KMType.SIGN,
+ KMType.EC,
+ KMType.SHA2_256,
+ KMType.PADDING_NONE,
+ (byte) 0,
+ deviceUniqueKeyPair,
+ null,
+ (short) 0,
+ (short) 0,
+ (short) 0);
+ if (operation[0] == null) {
+ KMException.throwIt(KMError.STATUS_FAILED);
+ }
+ }
+
+ private short getResponseProcessedLength(short index) {
+ short dataEntryIndex = getEntry(index);
+ if (dataEntryIndex == 0) {
+ dataEntryIndex = createEntry(index, SHORT_SIZE);
+ Util.setShort(data, dataEntryIndex, (short) 0);
+ return (short) 0;
+ }
+ return Util.getShort(data, dataEntryIndex);
+ }
+
+ private void updateResponseProcessedLength(short index, short processedLen) {
+ short dataEntryIndex = getEntry(index);
+ Util.setShort(data, dataEntryIndex, processedLen);
+ }
+
+ private short processUdsCertificateChain(byte[] scratchPad) {
+ byte[] persistedData = storeDataInst.getUdsCertChain();
+ short totalUccLen = Util.getShort(persistedData, (short) 0);
+ createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE);
+ if (totalUccLen == 0) {
+ // No Uds certificate chain present.
+ updateOutputProcessingState(PROCESSING_UDS_CERTS_COMPLETE);
+ return 0;
+ }
+ short processedLen = getResponseProcessedLength(UDS_PROCESSED_LENGTH);
+ short lengthToSend = (short) (totalUccLen - processedLen);
+ if (lengthToSend > MAX_SEND_DATA) {
+ lengthToSend = MAX_SEND_DATA;
+ }
+ Util.arrayCopyNonAtomic(
+ persistedData, (short) (2 + processedLen), scratchPad, (short) 0, lengthToSend);
+
+ processedLen += lengthToSend;
+ updateResponseProcessedLength(UDS_PROCESSED_LENGTH, processedLen);
+ // Update the output processing state.
+ updateOutputProcessingState(
+ (processedLen == totalUccLen)
+ ? PROCESSING_UDS_CERTS_COMPLETE
+ : PROCESSING_UDS_CERTS_IN_PROGRESS);
+ return lengthToSend;
+ }
+
+ // Dice cert chain for STRONGBOX has chain length of 2. So it can be returned in a single go.
+ private short processDiceCertChain(byte[] scratchPad) {
+ byte[] diceCertChain = storeDataInst.getDiceCertificateChain();
+ short totalDccLen = Util.getShort(diceCertChain, (short) 0);
+ if (totalDccLen == 0) {
+ // No Uds certificate chain present.
+ updateOutputProcessingState(PROCESSING_DICE_CERTS_COMPLETE);
+ return 0;
+ }
+ short processedLen = getResponseProcessedLength(DICE_PROCESSED_LENGTH);
+ short lengthToSend = (short) (totalDccLen - processedLen);
+ if (lengthToSend > MAX_SEND_DATA) {
+ lengthToSend = MAX_SEND_DATA;
+ }
+ Util.arrayCopyNonAtomic(
+ diceCertChain, (short) (2 + processedLen), scratchPad, (short) 0, lengthToSend);
+
+ processedLen += lengthToSend;
+ updateResponseProcessedLength(DICE_PROCESSED_LENGTH, processedLen);
+ // Update the output processing state.
+ updateOutputProcessingState(
+ (processedLen == totalDccLen)
+ ? PROCESSING_DICE_CERTS_COMPLETE
+ : PROCESSING_DICE_CERTS_IN_PROGRESS);
+ return lengthToSend;
+ }
+
+ private short constructCoseMacForRkpKey(boolean testMode, byte[] scratchPad, short pubKey) {
+ // prepare cosekey
+ short coseKey =
+ KMCose.constructCoseKey(
+ 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),
+ KMByteBlob.cast(pubKey).getBuffer(),
+ KMByteBlob.cast(pubKey).getStartOff(),
+ KMByteBlob.cast(pubKey).length(),
+ KMType.INVALID_VALUE,
+ testMode);
+ // Encode the cose key and make it as payload.
+ short len =
+ KMKeymasterApplet.encodeToApduBuffer(
+ coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ short payload = KMByteBlob.instance(scratchPad, (short) 0, len);
+ // Prepare protected header, which is required to construct the COSE_MAC0
+ short headerPtr =
+ KMCose.constructHeaders(
+ rkpTmpVariables,
+ KMInteger.uint_8(KMCose.COSE_ALG_HMAC_256),
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE,
+ KMType.INVALID_VALUE);
+ // Encode the protected header as byte blob.
+ len =
+ KMKeymasterApplet.encodeToApduBuffer(
+ headerPtr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ short protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, len);
+ // create MAC_Structure
+ short macStructure =
+ KMCose.constructCoseMacStructure(protectedHeader, KMByteBlob.instance((short) 0), payload);
+ // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0
+ len =
+ KMKeymasterApplet.encodeToApduBuffer(
+ macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ // HMAC Sign.
+ short hmacLen = rkpHmacSign(testMode, scratchPad, (short) 0, len, scratchPad, len);
+ // Create COSE_MAC0 object
+ short coseMac0 =
+ KMCose.constructCoseMac0(
+ protectedHeader,
+ KMCoseHeaders.instance(KMArray.instance((short) 0)),
+ payload,
+ KMByteBlob.instance(scratchPad, len, hmacLen));
+ len =
+ KMKeymasterApplet.encodeToApduBuffer(
+ coseMac0, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE);
+ return KMByteBlob.instance(scratchPad, (short) 0, len);
+ }
+
+ private short getEcAttestKeyParameters() {
+ short tagIndex = 0;
+ short arrPtr = KMArray.instance((short) 6);
+ // Key size - 256
+ short keySize =
+ KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short) 256));
+ // Digest - SHA256
+ short byteBlob = KMByteBlob.instance((short) 1);
+ KMByteBlob.cast(byteBlob).add((short) 0, KMType.SHA2_256);
+ short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob);
+ // Purpose - Attest
+ byteBlob = KMByteBlob.instance((short) 1);
+ KMByteBlob.cast(byteBlob).add((short) 0, KMType.ATTEST_KEY);
+ short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob);
+
+ KMArray.cast(arrPtr).add(tagIndex++, purpose);
+ // Algorithm - EC
+ KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC));
+ KMArray.cast(arrPtr).add(tagIndex++, keySize);
+ KMArray.cast(arrPtr).add(tagIndex++, digest);
+ // Curve - P256
+ KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ECCURVE, KMType.P_256));
+ // No Authentication is required to use this key.
+ KMArray.cast(arrPtr).add(tagIndex, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED));
+ return KMKeyParameters.instance(arrPtr);
+ }
+
+ private boolean isSignedByte(byte b) {
+ return ((b & 0x0080) != 0);
+ }
+
+ private short writeIntegerHeader(short valueLen, byte[] data, short offset) {
+ // write length
+ data[offset] = (byte) valueLen;
+ // write INTEGER tag
+ offset--;
+ data[offset] = 0x02;
+ return offset;
+ }
+
+ private short writeSequenceHeader(short valueLen, byte[] data, short offset) {
+ // write length
+ data[offset] = (byte) valueLen;
+ // write INTEGER tag
+ offset--;
+ data[offset] = 0x30;
+ return offset;
+ }
+
+ private short writeSignatureData(
+ byte[] input, short inputOff, short inputlen, byte[] output, short offset) {
+ Util.arrayCopyNonAtomic(input, inputOff, output, offset, inputlen);
+ if (isSignedByte(input[inputOff])) {
+ offset--;
+ output[offset] = (byte) 0;
+ }
+ return offset;
+ }
+
+ public short encodeES256CoseSignSignature(
+ byte[] input, short offset, short len, byte[] scratchPad, short scratchPadOff) {
+ // SEQ [ INTEGER(r), INTEGER(s)]
+ // write from bottom to the top
+ if (len != 64) {
+ KMException.throwIt(KMError.INVALID_DATA);
+ }
+ short maxTotalLen = 72;
+ short end = (short) (scratchPadOff + maxTotalLen);
+ // write s.
+ short start = (short) (end - 32);
+ start = writeSignatureData(input, (short) (offset + 32), (short) 32, scratchPad, start);
+ // write length and header
+ short length = (short) (end - start);
+ start--;
+ start = writeIntegerHeader(length, scratchPad, start);
+ // write r
+ short rEnd = start;
+ start = (short) (start - 32);
+ start = writeSignatureData(input, offset, (short) 32, scratchPad, start);
+ // write length and header
+ length = (short) (rEnd - start);
+ start--;
+ start = writeIntegerHeader(length, scratchPad, start);
+ // write length and sequence header
+ length = (short) (end - start);
+ start--;
+ start = writeSequenceHeader(length, scratchPad, start);
+ length = (short) (end - start);
+ if (start > scratchPadOff) {
+ // re adjust the buffer
+ Util.arrayCopyNonAtomic(scratchPad, start, scratchPad, scratchPadOff, length);
+ }
+ return length;
+ }
+
+ private short rkpHmacSign(
+ boolean testMode,
+ byte[] data,
+ short dataStart,
+ short dataLength,
+ byte[] signature,
+ short signatureStart) {
+ short result;
+ if (testMode) {
+ short macKey = KMByteBlob.instance(EPHEMERAL_MAC_KEY_SIZE);
+ Util.arrayFillNonAtomic(
+ KMByteBlob.cast(macKey).getBuffer(),
+ KMByteBlob.cast(macKey).getStartOff(),
+ EPHEMERAL_MAC_KEY_SIZE,
+ (byte) 0);
+ result =
+ seProvider.hmacSign(
+ KMByteBlob.cast(macKey).getBuffer(),
+ KMByteBlob.cast(macKey).getStartOff(),
+ EPHEMERAL_MAC_KEY_SIZE,
+ data,
+ dataStart,
+ dataLength,
+ signature,
+ signatureStart);
+ } else {
+ result =
+ seProvider.hmacSign(
+ storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart);
+ }
+ return result;
+ }
+}