From c2b351db6299fb896f54349894cd091692cb99e4 Mon Sep 17 00:00:00 2001 From: Yi Kong Date: Thu, 11 Aug 2022 16:09:24 +0800 Subject: Fix ThinLTO build failure Build fails with the following error: ld.lld: error: external/libese/tools/ese_relay/ese_relay.c:162:(.text.main+0x474): improper alignment for relocation R_AARCH64_LDST64_ABS_LO12_NC: 0xD85 is not aligned to 8 bytes ese_relay.c declares the variable as an extern pointer, thus must be aligned. However the code does not guarantee it to be aligned. Fixed by making sure that the declarations and definitions match. Bug: 241722362 Test: m GLOBAL_THINLTO=true ese-relay-fake Change-Id: I7548ca4ca6aedeb5ec7aac20536f82f0f1c1e9c6 --- tools/ese_relay/ese_relay_fake.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ese_relay/ese_relay_fake.c b/tools/ese_relay/ese_relay_fake.c index f336709..99b359e 100644 --- a/tools/ese_relay/ese_relay_fake.c +++ b/tools/ese_relay/ese_relay_fake.c @@ -20,7 +20,8 @@ ESE_INCLUDE_HW(ESE_HW_FAKE); /* Minimal ATR */ -const uint8_t kAtr[] = {0x00, 0x00}; +static const uint8_t kAtrBytes[] = {0x00, 0x00}; +const uint8_t *kAtr = &kAtrBytes[0]; const size_t kAtrLength = sizeof(kAtr); const void *kEseOpenData = NULL; -- cgit v1.2.3 From e5f11e02671627cd76d8fa4becc73020770fb491 Mon Sep 17 00:00:00 2001 From: Subrahmanyaman Date: Sat, 30 Apr 2022 00:07:00 +0000 Subject: Added android reay_se implementation of KeyMint Applet Test: run vts -m VtsAidlKeyMintTarget Change-Id: I839b6fe2f7a8a1eef9bed306539b421c39ddfff9 --- .../javacard/keymaster/KMAndroidSEApplet.java | 601 +++ .../javacard/keymaster/KMAttestationCertImpl.java | 1038 ++++ .../javacard/keymaster/KMConfigurations.java | 27 + .../com/android/javacard/keymaster/KMUtils.java | 436 ++ .../com/android/javacard/seprovider/KMAESKey.java | 47 + .../javacard/seprovider/KMAndroidSEProvider.java | 1574 ++++++ .../javacard/seprovider/KMAttestationCert.java | 198 + .../javacard/seprovider/KMAttestationKey.java | 23 + .../javacard/seprovider/KMComputedHmacKey.java | 3 + .../javacard/seprovider/KMDataStoreConstants.java | 11 + .../javacard/seprovider/KMDeviceUniqueKeyPair.java | 21 + .../javacard/seprovider/KMECDeviceUniqueKey.java | 54 + .../javacard/seprovider/KMECPrivateKey.java | 47 + .../seprovider/KMEcdsa256NoDigestSignature.java | 132 + .../com/android/javacard/seprovider/KMError.java | 32 + .../android/javacard/seprovider/KMException.java | 46 + .../com/android/javacard/seprovider/KMHmacKey.java | 47 + .../android/javacard/seprovider/KMKeyObject.java | 6 + .../android/javacard/seprovider/KMMasterKey.java | 23 + .../android/javacard/seprovider/KMOperation.java | 75 + .../javacard/seprovider/KMOperationImpl.java | 411 ++ .../android/javacard/seprovider/KMPoolManager.java | 657 +++ .../javacard/seprovider/KMPreSharedKey.java | 23 + .../android/javacard/seprovider/KMRkpMacKey.java | 3 + .../seprovider/KMRsa2048NoDigestSignature.java | 139 + .../javacard/seprovider/KMRsaOAEPEncoding.java | 288 ++ .../android/javacard/seprovider/KMSEProvider.java | 804 ++++ .../com/android/javacard/seprovider/KMType.java | 82 + .../android/javacard/seprovider/KMUpgradable.java | 29 + ready_se/google/keymint/KM200/Applet/README.md | 15 + .../com/android/javacard/keymaster/KMArray.java | 165 + .../android/javacard/keymaster/KMAsn1Parser.java | 434 ++ .../android/javacard/keymaster/KMBignumTag.java | 110 + .../com/android/javacard/keymaster/KMBoolTag.java | 115 + .../com/android/javacard/keymaster/KMByteBlob.java | 142 + .../com/android/javacard/keymaster/KMByteTag.java | 145 + .../src/com/android/javacard/keymaster/KMCose.java | 608 +++ .../javacard/keymaster/KMCoseCertPayload.java | 136 + .../android/javacard/keymaster/KMCoseHeaders.java | 203 + .../com/android/javacard/keymaster/KMCoseKey.java | 253 + .../com/android/javacard/keymaster/KMCoseMap.java | 171 + .../javacard/keymaster/KMCosePairByteBlobTag.java | 137 + .../javacard/keymaster/KMCosePairCoseKeyTag.java | 89 + .../javacard/keymaster/KMCosePairIntegerTag.java | 92 + .../keymaster/KMCosePairNegIntegerTag.java | 92 + .../keymaster/KMCosePairSimpleValueTag.java | 76 + .../javacard/keymaster/KMCosePairTagType.java | 258 + .../keymaster/KMCosePairTextStringTag.java | 91 + .../com/android/javacard/keymaster/KMDecoder.java | 774 +++ .../com/android/javacard/keymaster/KMEncoder.java | 772 +++ .../src/com/android/javacard/keymaster/KMEnum.java | 166 + .../android/javacard/keymaster/KMEnumArrayTag.java | 305 ++ .../com/android/javacard/keymaster/KMEnumTag.java | 152 + .../com/android/javacard/keymaster/KMError.java | 134 + .../javacard/keymaster/KMHardwareAuthToken.java | 171 + .../keymaster/KMHmacSharingParameters.java | 110 + .../com/android/javacard/keymaster/KMInteger.java | 215 + .../javacard/keymaster/KMIntegerArrayTag.java | 163 + .../android/javacard/keymaster/KMIntegerTag.java | 218 + .../javacard/keymaster/KMKeyCharacteristics.java | 124 + .../javacard/keymaster/KMKeyParameters.java | 472 ++ .../javacard/keymaster/KMKeymasterApplet.java | 5030 ++++++++++++++++++++ .../javacard/keymaster/KMKeymintDataStore.java | 1050 ++++ .../src/com/android/javacard/keymaster/KMMap.java | 202 + .../com/android/javacard/keymaster/KMNInteger.java | 130 + .../javacard/keymaster/KMOperationState.java | 354 ++ .../android/javacard/keymaster/KMRepository.java | 127 + .../android/javacard/keymaster/KMSemanticTag.java | 75 + .../android/javacard/keymaster/KMSimpleValue.java | 67 + .../src/com/android/javacard/keymaster/KMTag.java | 102 + .../android/javacard/keymaster/KMTextString.java | 80 + .../src/com/android/javacard/keymaster/KMType.java | 407 ++ .../javacard/keymaster/KMVerificationToken.java | 129 + .../RemotelyProvisionedComponentDevice.java | 1591 +++++++ 74 files changed, 23329 insertions(+) create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java create mode 100644 ready_se/google/keymint/KM200/Applet/README.md create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java new file mode 100644 index 0000000..27428ca --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -0,0 +1,601 @@ +/* + * 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" (short)0IS, + * 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.KMAndroidSEProvider; +import com.android.javacard.seprovider.KMException; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.OnUpgradeListener; +import org.globalplatform.upgrade.UpgradeManager; + +public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { + // Magic number version + private static final byte KM_MAGIC_NUMBER = (byte) 0x82; + // MSB byte is for Major version and LSB byte is for Minor version. + public static final short KM_APPLET_PACKAGE_VERSION = 0x0300; + + private static final byte KM_BEGIN_STATE = 0x00; + private static final byte ILLEGAL_STATE = KM_BEGIN_STATE + 1; + private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + + // Provider specific Commands + private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; + private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; + // Commands 4, 5 and 6 are reserved for vendor usage. + private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; + // 0x08 was reserved for INS_INIT_STRONGBOX_CMD + // 0x09 was reserved for INS_SET_BOOT_ENDED_CMD earlier. it is unused now. + private static final byte INS_SE_FACTORY_PROVISIONING_LOCK_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 10; + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 11; + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 12; + private static final byte INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 13; + private static final byte INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 14; + private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 15; + private static final byte INS_SET_BOOT_PARAMS_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 16; // Unused + private static final byte INS_OEM_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 17; + private static final byte INS_PROVISION_SECURE_BOOT_MODE_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 18; + + private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; + public static final byte BOOT_KEY_MAX_SIZE = 32; + public static final byte BOOT_HASH_MAX_SIZE = 32; + public static final byte SHARED_SECRET_KEY_SIZE = 32; + + // Package version. + protected short packageVersion; + + KMAndroidSEApplet() { + super(new KMAndroidSEProvider()); + packageVersion = KM_APPLET_PACKAGE_VERSION; + } + + /** + * Installs this applet. + * + * @param bArray the array containing installation parameters + * @param bOffset the starting offset in bArray + * @param bLength the length in bytes of the parameter data in bArray + */ + public static void install(byte[] bArray, short bOffset, byte bLength) { + new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + public void handleDeviceBooted() { + if (seProvider.isBootSignalEventSupported() && seProvider.isDeviceRebooted()) { + kmDataStore.clearDeviceBootStatus(); + super.reboot(); + seProvider.clearDeviceBooted(true); + } + } + + @Override + public void process(APDU apdu) { + try { + handleDeviceBooted(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (apduIns == KMType.INVALID_VALUE) { + return; + } + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + super.powerReset(); + } + + if (isCommandAllowed(apduIns)) { + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + processProvisionAttestIdsCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ATTEST_IDS); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_PRESHARED_SECRET_CMD: + processProvisionPreSharedSecretCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_PRESHARED_SECRET); + sendResponse(apdu, KMError.OK); + break; + + case INS_GET_PROVISION_STATUS_CMD: + processGetProvisionStatusCmd(apdu); + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + processProvisionRkpDeviceUniqueKeyPair(apdu); + break; + + case INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD: + processProvisionRkpAdditionalCertChain(apdu); + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + kmDataStore.setProvisionStatus(PROVISION_STATUS_SE_LOCKED); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + processProvisionOEMRootPublicKeyCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_OEM_PUBLIC_KEY); + sendResponse(apdu, KMError.OK); + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + processOEMLockProvisionCmd(apdu); + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + processOEMUnlockProvisionCmd(apdu); + break; + + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + processSecureBootCmd(apdu); + break; + + default: + super.process(apdu); + break; + } + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + } catch (KMException exception) { + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private boolean isCommandAllowed(short apduIns) { + boolean result = true; + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + case INS_PROVISION_PRESHARED_SECRET_CMD: + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + if (kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + if (!kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + if (isSeFactoryProvisioningLocked() || !isSeFactoryProvisioningComplete()) { + result = false; + } + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + // Allow lock only when + // 1. All the necessary provisioning commands are succcessfully executed + // 2. SE provision is locked + // 3. OEM Root Public is provisioned. + if (kmDataStore.isProvisionLocked() + || !(isProvisioningComplete() && isSeFactoryProvisioningLocked())) { + result = false; + } + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + case INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD: + if (isSeFactoryProvisioningLocked()) { + result = false; + } + break; + + case INS_GET_PROVISION_STATUS_CMD: + break; + + default: + // Allow other commands only if provision is completed. + if (!isProvisioningComplete()) { + result = false; + } + } + return result; + } + + private boolean isSeFactoryProvisioningLocked() { + short pStatus = kmDataStore.getProvisionStatus(); + boolean result = false; + if ((0 != (pStatus & PROVISION_STATUS_SE_LOCKED))) { + result = true; + } + return result; + } + + private boolean isSeFactoryProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + if (PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + == (pStatus & PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR)) { + return true; + } + return false; + } + + private void processSecureBootCmd(APDU apdu) { + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); + short args = receiveIncoming(apdu, argsProto); + short val = KMInteger.cast(KMArray.cast(args).get((short) 0)).getShort(); + if (val != 1 && val != 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Store secure boot mode value. + JCSystem.beginTransaction(); + kmDataStore.secureBootMode = (byte) val; + JCSystem.commitTransaction(); + kmDataStore.setProvisionStatus(PROVISION_STATUS_SECURE_BOOT_MODE); + sendResponse(apdu, KMError.OK); + } + + private void processOEMUnlockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_UNLOCK_PROVISION_VERIFICATION_LABEL, apdu); + kmDataStore.unlockProvision(); + sendResponse(apdu, KMError.OK); + } + + private void processOEMLockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_LOCK_PROVISION_VERIFICATION_LABEL, apdu); + // Enable the lock bit in provision status. + kmDataStore.setProvisionStatus(PROVISION_STATUS_PROVISIONING_LOCKED); + sendResponse(apdu, KMError.OK); + } + + private void authenticateOEM(byte[] plainMsg, APDU apdu) { + + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + short args = receiveIncoming(apdu, tmpVariables[0]); + // Get the signature input. + short signature = KMArray.cast(args).get((short) 0); + byte[] oemPublicKey = kmDataStore.getOEMRootPublicKey(); + + if (!seProvider.ecVerify256( + oemPublicKey, + (short) 0, + (short) oemPublicKey.length, + plainMsg, + (short) 0, + (short) plainMsg.length, + KMByteBlob.cast(signature).getBuffer(), + KMByteBlob.cast(signature).getStartOff(), + KMByteBlob.cast(signature).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormatPtr); + KMArray.cast(argsProto).add((short) 2, blob); + short args = receiveIncoming(apdu, argsProto); + + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + // Key format must be RAW format + byte keyFormat = KMEnum.cast(tmpVariables[0]).getVal(); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // get algorithm - only EC keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Purpose should be VERIFY + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.VERIFY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + tmpVariables[0] = KMArray.cast(args).get((short) 2); + // persist OEM Root Public Key. + kmDataStore.persistOEMRootPublicKey( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + } + + private static void processProvisionRkpDeviceUniqueKeyPair(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short arr = KMArray.instance((short) 1); + short coseKeyExp = KMCoseKey.exp(); + KMArray.cast(arr).add((short) 0, coseKeyExp); // [ CoseKey ] + arr = receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.cast(arr).get((short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + // Store the Device unique Key. + kmDataStore.createRkpDeviceUniqueKeyPair( + scratchPad, (short) 0, pubKeyLen, scratchPad, pubKeyLen, privKeyLen); + short bcc = generateBcc(false, scratchPad); + short len = KMKeymasterApplet.encodeToApduBuffer(bcc, scratchPad, (short) 0, MAX_COSE_BUF_SIZE); + kmDataStore.persistBootCertificateChain(scratchPad, (short) 0, len); + kmDataStore.setProvisionStatus(PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionRkpAdditionalCertChain(APDU apdu) { + // X509 certificate chain is received as shown below: + /** + * x509CertChain = bstr .cbor UdsCerts + * + *

AdditionalDKSignatures = { * SignerName => DKCertChain } ; SignerName is a string + * identifier that indicates both the signing authority as ; well as the format of the + * DKCertChain SignerName = tstr + * + *

DKCertChain = [ 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor self-signed + * ; cert, "Leaf" contains DK_pub. There may also be ; intermediate certificates between Root + * and Leaf. ] ; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or edDSA) + * X509Certificate = bstr + */ + // Store the cbor encoded UdsCerts as it is in the persistent memory so cbor decoding is + // required here. + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + 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 byteHeaderLen = + decoder.readCertificateChainHeaderLen(buffer, bufferStartOffset, bufferLength); + kmDataStore.persistAdditionalCertChain( + buffer, + (short) (bufferStartOffset + byteHeaderLen), + (short) (bufferLength - byteHeaderLen)); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ADDITIONAL_CERT_CHAIN); + // reclaim memory + repository.reclaimMemory(bufferLength); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionAttestIdsCmd(APDU apdu) { + short keyparams = KMKeyParameters.exp(); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, keyparams); + short args = receiveIncoming(apdu, cmd); + + short attData = KMArray.cast(args).get((short) 0); + // persist attestation Ids - if any is missing then exception occurs + setAttestationIds(attData); + } + + public void setAttestationIds(short attIdVals) { + KMKeyParameters instParam = KMKeyParameters.cast(attIdVals); + KMArray vals = KMArray.cast(instParam.getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + + if (KMType.BYTES_TAG != type) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + obj = KMByteTag.cast(obj).getValue(); + if (KMByteBlob.cast(obj).length() > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + kmDataStore.setAttestationId( + key, + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + index++; + } + } + + private void processProvisionPreSharedSecretCmd(APDU apdu) { + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, blob); + short args = receiveIncoming(apdu, argsProto); + + short val = KMArray.cast(args).get((short) 0); + + if (val != KMType.INVALID_VALUE && KMByteBlob.cast(val).length() != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Persist shared Hmac. + kmDataStore.createPresharedKey( + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + } + + // This function masks the error code with POWER_RESET_MASK_FLAG + // in case if card reset event occurred. The clients of the Applet + // has to extract the power reset status from the error code and + // process accordingly. + private static short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + KMInteger.cast(int32Ptr).getStartOff(), + powerResetStatus); + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + (short) (KMInteger.cast(int32Ptr).getStartOff() + 2), + err); + // reset power reset status flag to its default value. + // repository.restorePowerResetStatus(); //TODO + return int32Ptr; + } + + private void processGetProvisionStatusCmd(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + short pStatus = kmDataStore.getProvisionStatus(); + Util.setShort(scratchpad, (short) 0, pStatus); + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); + KMArray.cast(resp).add((short) 1, KMInteger.instance(scratchpad, (short) 0, (short) 2)); + sendOutgoing(apdu, resp); + } + + private boolean isProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + short pCompleteStatus = + PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + | PROVISION_STATUS_PRESHARED_SECRET + | PROVISION_STATUS_ATTEST_IDS + | PROVISION_STATUS_OEM_PUBLIC_KEY + | PROVISION_STATUS_SECURE_BOOT_MODE; + if (kmDataStore.isProvisionLocked() || (pCompleteStatus == (pStatus & pCompleteStatus))) { + return true; + } + return false; + } + + @Override + public void onCleanup() {} + + @Override + public void onConsolidate() {} + + private boolean isUpgradeAllowed(short oldVersion) { + boolean upgradeAllowed = false; + // Downgrade of the Applet is not allowed. + if (KM_APPLET_PACKAGE_VERSION >= oldVersion) { + upgradeAllowed = true; + } + return upgradeAllowed; + } + + @Override + public void onRestore(Element element) { + element.initRead(); + byte magicNumber = element.readByte(); + if (magicNumber != KM_MAGIC_NUMBER) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short oldPackageVersion = element.readShort(); + // Validate version. + if (!isUpgradeAllowed(oldPackageVersion)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + kmDataStore.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + } + + @Override + public Element onSave() { + short primitiveCount = 3; + primitiveCount += kmDataStore.getBackupPrimitiveByteCount(); + short objectCount = kmDataStore.getBackupObjectCount(); + // Create element. + Element element = + UpgradeManager.createElement(Element.TYPE_SIMPLE, primitiveCount, objectCount); + + element.write(KM_MAGIC_NUMBER); + element.write(packageVersion); + kmDataStore.onSave(element); + return element; + } + + private short validateApdu(APDU apdu) { + // Read the apdu header and buffer. + byte[] apduBuffer = apdu.getBuffer(); + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate CLA + if (!apdu.isValidCLA()) { + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + } + + // Validate P1P2. + if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { + sendResponse(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + return apduBuffer[ISO7816.OFFSET_INS]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java new file mode 100644 index 0000000..90bc3fe --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -0,0 +1,1038 @@ +/* + * 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" (short)0IS, + * 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.KMAESKey; +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMMasterKey; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +// The class encodes strongbox generated amd signed attestation certificate. This only encodes +// required fields of the certificates. It is not meant to be generic X509 cert encoder. +// Whatever fields that are fixed are added as byte arrays. The Extensions are encoded as per +// the values. +// The certificate is assembled with leafs first and then the sequences. + +public class KMAttestationCertImpl implements KMAttestationCert { + + private static final byte MAX_PARAMS = 30; + // DER encoded object identifiers required by the cert. + // rsaEncryption - 1.2.840.113549.1.1.1 + private static final byte[] rsaEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + // ecPublicKey - 1.2.840.10045.2.1 + private static final byte[] eccPubKey = { + 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 + }; + // prime256v1 curve - 1.2.840.10045.3.1.7 + private static final byte[] prime256v1 = { + 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07 + }; + // Key Usage Extn - 2.5.29.15 + private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + // Android Extn - 1.3.6.1.4.1.11129.2.1.17 + private static final byte[] androidExtn = { + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + private static final short RSA_SIG_LEN = 256; + private static final byte ECDSA_MAX_SIG_LEN = 72; + // Signature algorithm identifier - ecdsaWithSha256 - 1.2.840.10045.4.3.2 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509EcdsaSignAlgIdentifier = { + 0x30, 0x0A, 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, (byte) 0x3D, 0x04, 0x03, 0x02 + }; + // Signature algorithm identifier - sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509RsaSignAlgIdentifier = { + 0x30, 0x0D, 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, + 0x0B, 0x05, 0x00 + }; + + // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] swTagIds = { + KMType.ATTESTATION_APPLICATION_ID, + KMType.CREATION_DATETIME, + KMType.ALLOW_WHILE_ON_BODY, + KMType.USAGE_COUNT_LIMIT, + KMType.USAGE_EXPIRE_DATETIME, + KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.ACTIVE_DATETIME, + }; + + // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] hwTagIds = { + KMType.BOOT_PATCH_LEVEL, + KMType.VENDOR_PATCH_LEVEL, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_SERIAL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_BRAND, + KMType.OS_PATCH_LEVEL, + KMType.OS_VERSION, + KMType.ROOT_OF_TRUST, + KMType.ORIGIN, + KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.TRUSTED_CONFIRMATION_REQUIRED, + KMType.AUTH_TIMEOUT, + KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, + KMType.EARLY_BOOT_ONLY, + KMType.ROLLBACK_RESISTANCE, + KMType.RSA_OAEP_MGF_DIGEST, + KMType.RSA_PUBLIC_EXPONENT, + KMType.ECCURVE, + KMType.PADDING, + KMType.DIGEST, + KMType.KEYSIZE, + KMType.ALGORITHM, + KMType.PURPOSE + }; + + private static final byte keyUsageSign = (byte) 0x80; // 0 bit + private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit + private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit + private static final byte keyUsageKeyAgreement = (byte) 0x08; // 4th- bit + private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit + + private static final short KEYMINT_VERSION = 200; + private static final short ATTESTATION_VERSION = 200; + private static final byte[] pubExponent = {0x01, 0x00, 0x01}; + private static final byte X509_VERSION = (byte) 0x02; + + // Buffer indexes in transient array + private static final byte NUM_INDEX_ENTRIES = 21; + private static final byte CERT_START = (byte) 0; + private static final byte CERT_LENGTH = (byte) 1; + private static final byte TBS_START = (byte) 2; + private static final byte TBS_LENGTH = (byte) 3; + private static final byte BUF_START = (byte) 4; + private static final byte BUF_LENGTH = (byte) 5; + private static final byte SW_PARAM_INDEX = (byte) 6; + private static final byte HW_PARAM_INDEX = (byte) 7; + // Data indexes in transient array + private static final byte STACK_PTR = (byte) 8; + private static final byte UNIQUE_ID = (byte) 9; + private static final byte ATT_CHALLENGE = (byte) 10; + private static final byte NOT_BEFORE = (byte) 11; + private static final byte NOT_AFTER = (byte) 12; + private static final byte PUB_KEY = (byte) 13; + private static final byte VERIFIED_BOOT_KEY = (byte) 14; + private static final byte VERIFIED_HASH = (byte) 15; + private static final byte ISSUER = (byte) 16; + private static final byte SUBJECT_NAME = (byte) 17; + private static final byte SERIAL_NUMBER = (byte) 18; + private static final byte CERT_ATT_KEY_SECRET = (byte) 19; + private static final byte CERT_ATT_KEY_RSA_PUB_MOD = (byte) 20; + // State indexes in transient array + private static final byte NUM_STATE_ENTRIES = 7; + private static final byte KEY_USAGE = (byte) 0; + private static final byte UNUSED_BITS = (byte) 1; + private static final byte DEVICE_LOCKED = (byte) 2; + private static final byte VERIFIED_STATE = (byte) 3; + private static final byte CERT_MODE = (byte) 4; + private static final byte RSA_CERT = (byte) 5; + private static final byte CERT_RSA_SIGN = (byte) 6; + + private static KMAttestationCert inst; + private static KMSEProvider seProvider; + + private static short[] indexes; + private static byte[] states; + + private static byte[] stack; + private static short[] swParams; + private static short[] hwParams; + + private static final byte SERIAL_NUM_MAX_LEN = 20; + + private KMAttestationCertImpl() {} + + public static KMAttestationCert instance(boolean rsaCert, KMSEProvider provider) { + if (inst == null) { + inst = new KMAttestationCertImpl(); + seProvider = provider; + + // Allocate transient memory + indexes = JCSystem.makeTransientShortArray(NUM_INDEX_ENTRIES, JCSystem.CLEAR_ON_RESET); + states = JCSystem.makeTransientByteArray(NUM_STATE_ENTRIES, JCSystem.CLEAR_ON_RESET); + swParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + hwParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + } + init(rsaCert); + return inst; + } + + private static void init(boolean rsaCert) { + for (short i = 0; i < NUM_INDEX_ENTRIES; i++) { + indexes[i] = 0; + } + Util.arrayFillNonAtomic(states, (short) 0, NUM_STATE_ENTRIES, (byte) 0); + stack = null; + states[CERT_MODE] = KMType.NO_CERT; + states[UNUSED_BITS] = 8; + states[RSA_CERT] = rsaCert ? (byte) 1 : (byte) 0; + states[CERT_RSA_SIGN] = 1; + indexes[CERT_ATT_KEY_SECRET] = KMType.INVALID_VALUE; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + indexes[ISSUER] = KMType.INVALID_VALUE; + indexes[SUBJECT_NAME] = KMType.INVALID_VALUE; + indexes[SERIAL_NUMBER] = KMType.INVALID_VALUE; + } + + @Override + public KMAttestationCert verifiedBootHash(short obj) { + indexes[VERIFIED_HASH] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootKey(short obj) { + indexes[VERIFIED_BOOT_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootState(byte val) { + states[VERIFIED_STATE] = val; + return this; + } + + private KMAttestationCert uniqueId(short obj) { + indexes[UNIQUE_ID] = obj; + return this; + } + + @Override + public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad) { + if (!derEncoded) { + // convert milliseconds to UTC date + indexes[NOT_BEFORE] = KMUtils.convertToDate(obj, scratchpad, true); + } else { + indexes[NOT_BEFORE] = + KMByteBlob.instance( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + return this; + } + + @Override + public KMAttestationCert notAfter( + short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad) { + if (!derEncoded) { + if (usageExpiryTimeObj != KMType.INVALID_VALUE) { + // compare if the expiry time is greater then 2050 then use generalized + // time format else use utc time format. + short tmpVar = KMInteger.uint_64(KMUtils.firstJan2050, (short) 0); + if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, false); + } else { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, true); + } + indexes[NOT_AFTER] = usageExpiryTimeObj; + } else { + // notAfter = certExpirtyTimeObj; + } + } else { + indexes[NOT_AFTER] = usageExpiryTimeObj; + } + return this; + } + + @Override + public KMAttestationCert deviceLocked(boolean val) { + if (val) { + states[DEVICE_LOCKED] = (byte) 0xFF; + } else { + states[DEVICE_LOCKED] = 0; + } + return this; + } + + @Override + public KMAttestationCert publicKey(short obj) { + indexes[PUB_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert attestationChallenge(short obj) { + indexes[ATT_CHALLENGE] = obj; + return this; + } + + @Override + public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { + if (hwEnforced) { + hwParams[indexes[HW_PARAM_INDEX]] = tag; + indexes[HW_PARAM_INDEX]++; + } else { + swParams[indexes[SW_PARAM_INDEX]] = tag; + indexes[SW_PARAM_INDEX]++; + } + if (KMTag.getKey(tag) == KMType.PURPOSE) { + createKeyUsage(tag); + } + return this; + } + + @Override + public KMAttestationCert issuer(short obj) { + indexes[ISSUER] = obj; + return this; + } + + private void createKeyUsage(short tag) { + short len = KMEnumArrayTag.cast(tag).length(); + byte index = 0; + while (index < len) { + if (KMEnumArrayTag.cast(tag).get(index) == KMType.SIGN) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageSign); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageDataEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyAgreement); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageCertSign); + } + index++; + } + index = states[KEY_USAGE]; + while (index != 0) { + index = (byte) (index << 1); + states[UNUSED_BITS]--; + } + } + + private static void pushTbsCert(boolean rsaCert, boolean rsa) { + short last = indexes[STACK_PTR]; + pushExtensions(); + // subject public key info + if (rsaCert) { + pushRsaSubjectKeyInfo(); + } else { + pushEccSubjectKeyInfo(); + } + // subject + pushBytes( + KMByteBlob.cast(indexes[SUBJECT_NAME]).getBuffer(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).getStartOff(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).length()); + pushValidity(); + // issuer - der encoded + pushBytes( + KMByteBlob.cast(indexes[ISSUER]).getBuffer(), + KMByteBlob.cast(indexes[ISSUER]).getStartOff(), + KMByteBlob.cast(indexes[ISSUER]).length()); + // Algorithm Id + if (rsa) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + // Serial Number + pushBytes( + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getBuffer(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getStartOff(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + pushIntegerHeader(KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + // Version + pushByte(X509_VERSION); + pushIntegerHeader((short) 1); + pushByte((byte) 0x03); + pushByte((byte) 0xA0); + // Finally sequence header. + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushExtensions() { + short last = indexes[STACK_PTR]; + // Push KeyUsage extension + if (states[KEY_USAGE] != 0) { + pushKeyUsage(states[KEY_USAGE], states[UNUSED_BITS]); + } + if (states[CERT_MODE] == KMType.ATTESTATION_CERT) { + pushKeyDescription(); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // Extensions have explicit tag of [3] + pushLength((short) (last - indexes[STACK_PTR])); + pushByte((byte) 0xA3); + } + + // Time SEQUENCE{UTCTime, UTC or Generalized Time) + private static void pushValidity() { + short last = indexes[STACK_PTR]; + if (indexes[NOT_AFTER] != 0) { + pushBytes( + KMByteBlob.cast(indexes[NOT_AFTER]).getBuffer(), + KMByteBlob.cast(indexes[NOT_AFTER]).getStartOff(), + KMByteBlob.cast(indexes[NOT_AFTER]).length()); + } else { + KMException.throwIt(KMError.INVALID_DATA); + } + pushTimeHeader(KMByteBlob.cast(indexes[NOT_AFTER]).length()); + pushBytes( + KMByteBlob.cast(indexes[NOT_BEFORE]).getBuffer(), + KMByteBlob.cast(indexes[NOT_BEFORE]).getStartOff(), + KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushTimeHeader(KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushTimeHeader(short len) { + if (len == 13) { // UTC Time + pushLength((short) 0x0D); + pushByte((byte) 0x17); + } else if (len == 15) { // Generalized Time + pushLength((short) 0x0F); + pushByte((byte) 0x18); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + + // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public + // exponent + // as positive integer} + private static void pushRsaSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes(pubExponent, (short) 0, (short) pubExponent.length); + pushIntegerHeader((short) pubExponent.length); + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + + // encode modulus as positive if the MSB is 1. + if (KMByteBlob.cast(indexes[PUB_KEY]).get((short) 0) < 0) { + pushByte((byte) 0x00); + pushIntegerHeader((short) (KMByteBlob.cast(indexes[PUB_KEY]).length() + 1)); + } else { + pushIntegerHeader(KMByteBlob.cast(indexes[PUB_KEY]).length()); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushBitStringHeader((byte) 0x00, (short) (last - indexes[STACK_PTR])); + pushRsaEncryption(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} + private static void pushEccSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushBitStringHeader((byte) 0x00, KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushEcDsa(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEcDsa() { + short last = indexes[STACK_PTR]; + pushBytes(prime256v1, (short) 0, (short) prime256v1.length); + pushBytes(eccPubKey, (short) 0, (short) eccPubKey.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushRsaEncryption() { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // KeyDescription ::= SEQUENCE { + // attestationVersion INTEGER, # Value 200 + // attestationSecurityLevel SecurityLevel, # See below + // keymasterVersion INTEGER, # Value 200 + // keymasterSecurityLevel SecurityLevel, # See below + // attestationChallenge OCTET_STRING, # Tag::ATTESTATION_CHALLENGE from attestParams + // uniqueId OCTET_STRING, # Empty unless key has Tag::INCLUDE_UNIQUE_ID + // softwareEnforced AuthorizationList, # See below + // hardwareEnforced AuthorizationList, # See below + // } + private static void pushKeyDescription() { + short last = indexes[STACK_PTR]; + pushHWParams(); + pushSWParams(); + if (indexes[UNIQUE_ID] != 0) { + pushOctetString( + KMByteBlob.cast(indexes[UNIQUE_ID]).getBuffer(), + KMByteBlob.cast(indexes[UNIQUE_ID]).getStartOff(), + KMByteBlob.cast(indexes[UNIQUE_ID]).length()); + } else { + pushOctetStringHeader((short) 0); + } + pushOctetString( + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getBuffer(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getStartOff(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).length()); + pushEnumerated(KMType.STRONGBOX); + pushShort(KEYMINT_VERSION); + pushIntegerHeader((short) 2); + pushEnumerated(KMType.STRONGBOX); + pushShort(ATTESTATION_VERSION); + pushIntegerHeader((short) 2); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(androidExtn, (short) 0, (short) androidExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushSWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) swTagIds.length; + do { + pushParams(swParams, indexes[SW_PARAM_INDEX], swTagIds[index]); + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushHWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) hwTagIds.length; + do { + if (hwTagIds[index] == KMType.ROOT_OF_TRUST) { + pushRoT(); + continue; + } + if (pushParams(hwParams, indexes[HW_PARAM_INDEX], hwTagIds[index])) { + continue; + } + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static boolean pushParams(short[] params, short len, short tagId) { + short index = 0; + while (index < len) { + if (tagId == KMTag.getKey(params[index])) { + pushTag(params[index]); + return true; + } + index++; + } + return false; + } + + private static void pushTag(short tag) { + short type = KMTag.getTagType(tag); + short tagId = KMTag.getKey(tag); + short val; + switch (type) { + case KMType.BYTES_TAG: + val = KMByteTag.cast(tag).getValue(); + pushBytesTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.ENUM_TAG: + val = KMEnumTag.cast(tag).getValue(); + pushEnumTag(tagId, (byte) val); + break; + case KMType.ENUM_ARRAY_TAG: + val = KMEnumArrayTag.cast(tag).getValues(); + pushEnumArrayTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + val = KMIntegerTag.cast(tag).getValue(); + pushIntegerTag( + tagId, + KMInteger.cast(val).getBuffer(), + KMInteger.cast(val).getStartOff(), + KMInteger.cast(val).length()); + break; + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + // According to KeyMint hal only one user secure id is used but this conflicts with + // tag type which is ULONG-REP. Currently this is encoded as SET OF INTEGERS + val = KMIntegerArrayTag.cast(tag).getValues(); + pushIntegerArrayTag(tagId, val); + break; + case KMType.BOOL_TAG: + val = KMBoolTag.cast(tag).getVal(); + pushBoolTag(tagId); + break; + default: + KMException.throwIt(KMError.INVALID_TAG); + break; + } + } + + // RootOfTrust ::= SEQUENCE { + // verifiedBootKey OCTET_STRING, + // deviceLocked BOOLEAN, + // verifiedBootState VerifiedBootState, + // verifiedBootHash OCTET_STRING, + // } + // VerifiedBootState ::= ENUMERATED { + // Verified (0), + // SelfSigned (1), + // Unverified (2), + // Failed (3), + // } + private static void pushRoT() { + short last = indexes[STACK_PTR]; + // verified boot hash + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_HASH]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).length()); + + pushEnumerated(states[VERIFIED_STATE]); + + pushBoolean(states[DEVICE_LOCKED]); + // verified boot Key + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).length()); + + // Finally sequence header + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // ... and tag Id + pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - indexes[STACK_PTR])); + } + + private static void pushOctetString(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + pushOctetStringHeader(len); + } + + private static void pushBoolean(byte val) { + pushByte(val); + pushBooleanHeader((short) 1); + } + + private static void pushBooleanHeader(short len) { + pushLength(len); + pushByte((byte) 0x01); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + short index = 0; + while (index < len) { + pushByte(buf[(short) (start + index)]); + pushIntegerHeader((short) 1); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushIntegerArrayTag(short tagId, short arr) { + short last = indexes[STACK_PTR]; + short index = 0; + short len = KMArray.cast(arr).length(); + short ptr; + while (index < len) { + ptr = KMArray.cast(arr).get(index); + pushInteger( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushSetHeader(short len) { + pushLength(len); + pushByte((byte) 0x31); + } + + private static void pushEnumerated(byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushEnumeratedHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEnumeratedHeader(short len) { + pushLength(len); + pushByte((byte) 0x0A); + } + + private static void pushBoolTag(short tagId) { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushNullHeader() { + pushByte((byte) 0); + pushByte((byte) 0x05); + } + + private static void pushEnumTag(short tagId, byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushInteger(buf, start, len); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Ignore leading zeros. Only Unsigned Integers are required hence if MSB is set then add 0x00 + // as most significant byte. + private static void pushInteger(byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + byte index = 0; + while (index < (byte) len) { + if (buf[(short) (start + index)] != 0) { + break; + } + index++; + } + if (index == (byte) len) { + pushByte((byte) 0x00); + } else { + pushBytes(buf, (short) (start + index), (short) (len - index)); + if (buf[(short) (start + index)] < 0) { // MSB is 1 + pushByte((byte) 0x00); // always unsigned int + } + } + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + } + + // Bytes Tag is a octet string and tag id is added explicitly + private static void pushBytesTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushBytes(buf, start, len); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // tag id <= 30 ---> 0xA0 | {tagId} + // 30 < tagId < 128 ---> 0xBF 0x{tagId} + // tagId >= 128 ---> 0xBF 0x80+(tagId/128) 0x{tagId - (128*(tagId/128))} + private static void pushTagIdHeader(short tagId, short len) { + pushLength(len); + short count = (short) (tagId / 128); + if (count > 0) { + pushByte((byte) (tagId - (128 * count))); + pushByte((byte) (0x80 + count)); + pushByte((byte) 0xBF); + } else if (tagId > 30) { + pushByte((byte) tagId); + pushByte((byte) 0xBF); + } else { + pushByte((byte) (0xA0 | (byte) tagId)); + } + } + + // SEQUENCE {ObjId, OCTET STRING{BIT STRING{keyUsage}}} + private static void pushKeyUsage(byte keyUsage, byte unusedBits) { + short last = indexes[STACK_PTR]; + pushByte(keyUsage); + pushBitStringHeader(unusedBits, (short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushAlgorithmId(byte[] algId) { + pushBytes(algId, (short) 0, (short) algId.length); + } + + private static void pushIntegerHeader(short len) { + pushLength(len); + pushByte((byte) 0x02); + } + + private static void pushOctetStringHeader(short len) { + pushLength(len); + pushByte((byte) 0x04); + } + + private static void pushSequenceHeader(short len) { + pushLength(len); + pushByte((byte) 0x30); + } + + private static void pushBitStringHeader(byte unusedBits, short len) { + pushByte(unusedBits); + pushLength((short) (len + 1)); // 1 extra byte for unused bits byte + pushByte((byte) 0x03); + } + + private static void pushLength(short len) { + if (len < 128) { + pushByte((byte) len); + } else if (len < 256) { + pushByte((byte) len); + pushByte((byte) 0x81); + } else { + pushShort(len); + pushByte((byte) 0x82); + } + } + + private static void pushShort(short val) { + decrementStackPtr((short) 2); + Util.setShort(stack, indexes[STACK_PTR], val); + } + + private static void pushByte(byte val) { + decrementStackPtr((short) 1); + stack[indexes[STACK_PTR]] = val; + } + + private static void pushBytes(byte[] buf, short start, short len) { + decrementStackPtr(len); + if (buf != null) { + Util.arrayCopyNonAtomic(buf, start, stack, indexes[STACK_PTR], len); + } + } + + private static void decrementStackPtr(short cnt) { + indexes[STACK_PTR] = (short) (indexes[STACK_PTR] - cnt); + if (indexes[BUF_START] > indexes[STACK_PTR]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + @Override + public KMAttestationCert buffer(byte[] buf, short start, short maxLen) { + stack = buf; + indexes[BUF_START] = start; + indexes[BUF_LENGTH] = maxLen; + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + return this; + } + + @Override + public short getCertStart() { + return indexes[CERT_START]; + } + + @Override + public short getCertLength() { + return indexes[CERT_LENGTH]; + } + + public void build(short attSecret, short attMod, boolean rsaSign, boolean fakeCert) { + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + short last = indexes[STACK_PTR]; + short sigLen = 0; + if (fakeCert) { + rsaSign = true; + pushByte((byte) 0); + sigLen = 1; + } + // Push placeholder signature Bit string header + // This will potentially change at the end + else if (rsaSign) { + decrementStackPtr(RSA_SIG_LEN); + } else { + decrementStackPtr(ECDSA_MAX_SIG_LEN); + } + short signatureOffset = indexes[STACK_PTR]; + pushBitStringHeader((byte) 0, (short) (last - indexes[STACK_PTR])); + if (rsaSign) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + indexes[TBS_LENGTH] = indexes[STACK_PTR]; + pushTbsCert((states[RSA_CERT] == 0 ? false : true), rsaSign); + indexes[TBS_START] = indexes[STACK_PTR]; + indexes[TBS_LENGTH] = (short) (indexes[TBS_LENGTH] - indexes[TBS_START]); + if (attSecret != KMType.INVALID_VALUE) { + // Sign with the attestation key + // The pubKey is the modulus. + if (rsaSign) { + sigLen = + seProvider.rsaSign256Pkcs1( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + KMByteBlob.cast(attMod).getBuffer(), + KMByteBlob.cast(attMod).getStartOff(), + KMByteBlob.cast(attMod).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > RSA_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } else { + sigLen = + seProvider.ecSign256( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Adjust signature length + indexes[STACK_PTR] = signatureOffset; + pushBitStringHeader((byte) 0, sigLen); + } else if (!fakeCert) { // No attestation key provisioned in the factory + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } + last = (short) (signatureOffset + sigLen); + // Add certificate sequence header + indexes[STACK_PTR] = indexes[TBS_START]; + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + indexes[CERT_START] = indexes[STACK_PTR]; + indexes[CERT_LENGTH] = (short) (last - indexes[CERT_START]); + } + + @Override + public void build() { + if (states[CERT_MODE] == KMType.FAKE_CERT) { + build(KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); + } else { + build( + indexes[CERT_ATT_KEY_SECRET], + indexes[CERT_ATT_KEY_RSA_PUB_MOD], + (states[CERT_RSA_SIGN] == 0 ? false : true), + false); + } + } + + @Override + public KMAttestationCert makeUniqueId( + byte[] scratchPad, + short scratchPadOff, + byte[] creationTime, + short timeOffset, + short creationTimeLen, + byte[] attestAppId, + short appIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMMasterKey masterKey) { + // Concatenate T||C||R + // temporal count T + short temp = + KMUtils.countTemporalCount( + creationTime, timeOffset, creationTimeLen, scratchPad, scratchPadOff); + Util.setShort(scratchPad, (short) scratchPadOff, temp); + temp = scratchPadOff; + scratchPadOff += 2; + + // Application Id C + Util.arrayCopyNonAtomic(attestAppId, appIdOff, scratchPad, scratchPadOff, attestAppIdLen); + scratchPadOff += attestAppIdLen; + + // Reset After Rotation R + scratchPad[scratchPadOff] = resetSinceIdRotation; + scratchPadOff++; + + // Get the key data from the master key + KMAESKey aesKey = (KMAESKey) masterKey; + short mKeyData = KMByteBlob.instance((short) (aesKey.aesKey.getSize() / 8)); + aesKey.aesKey.getKey( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ + timeOffset = KMByteBlob.instance((short) 32); + appIdOff = + seProvider.hmacSign( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff(), /* Key start*/ + KMByteBlob.cast(mKeyData).length(), /* Key length*/ + scratchPad, /* data */ + temp, /* data start */ + scratchPadOff, /* data length */ + KMByteBlob.cast(timeOffset).getBuffer(), /* signature buffer */ + KMByteBlob.cast(timeOffset).getStartOff()); /* signature start */ + if (appIdOff != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return uniqueId(timeOffset); + } + + @Override + public boolean serialNumber(short number) { + // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 + short length = KMByteBlob.cast(number).length(); + if (length > SERIAL_NUM_MAX_LEN) { + return false; + } + // The serial number Must be a positive integer. + byte msb = KMByteBlob.cast(number).get((short) 0); + if (msb < 0 && length > (SERIAL_NUM_MAX_LEN - 1)) { + return false; + } + indexes[SERIAL_NUMBER] = number; + return true; + } + + @Override + public boolean subjectName(short sub) { + if (sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; + indexes[SUBJECT_NAME] = sub; + return true; + } + + @Override + public KMAttestationCert ecAttestKey(short attestKey, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestKey; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + states[CERT_RSA_SIGN] = 0; + return this; + } + + @Override + public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestPrivExp; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = attestMod; + states[CERT_RSA_SIGN] = 1; + return this; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java new file mode 100644 index 0000000..3fb3653 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java @@ -0,0 +1,27 @@ +/* + * 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; + +public class KMConfigurations { + // Machine types + public static final byte LITTLE_ENDIAN = 0x00; + public static final byte BIG_ENDIAN = 0x01; + public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN; + // If the size of the attestation ids is known and lesser than 64 + // then reduce the size here. It reduces the heap memory usage. + public static final byte MAX_ATTESTATION_IDS_SIZE = 64; + public static final short MAX_SUBJECT_DER_LEN = 1095; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java new file mode 100644 index 0000000..bed3ba7 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -0,0 +1,436 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.Util; + +public class KMUtils { + + // 64 bit unsigned calculations for time + public static final byte[] oneSecMsec = {0, 0, 0, 0, 0, 0, 0x03, (byte) 0xE8}; // 1000 msec + public static final byte[] oneMinMsec = {0, 0, 0, 0, 0, 0, (byte) 0xEA, 0x60}; // 60000 msec + public static final byte[] oneHourMsec = { + 0, 0, 0, 0, 0, 0x36, (byte) 0xEE, (byte) 0x80 + }; // 3600000 msec + public static final byte[] oneDayMsec = {0, 0, 0, 0, 0x05, 0x26, 0x5C, 0x00}; // 86400000 msec + public static final byte[] oneMonthMsec = { + 0, 0, 0, 0, (byte) 0x9C, (byte) 0xBE, (byte) 0xBD, 0x50 + }; // 2629746000 msec + public static final byte[] leapYearMsec = { + 0, 0, 0, 0x07, (byte) 0x5C, (byte) 0xD7, (byte) 0x88, 0x00 + }; // 31622400000; + public static final byte[] yearMsec = { + 0, 0, 0, 0x07, 0x57, (byte) 0xB1, 0x2C, 0x00 + }; // 31536000000 + // Leap year(366) + 3 * 365 + public static final byte[] fourYrsMsec = { + 0, 0, 0, 0x1D, 0x63, (byte) 0xEB, 0x0C, 0x00 + }; // 126230400000 + public static final byte[] firstJan2020 = { + 0, 0, 0x01, 0x6F, 0x5E, 0x66, (byte) 0xE8, 0x00 + }; // 1577836800000 msec + public static final byte[] firstJan2050 = { + 0, 0, 0x02, 0x4b, (byte) 0xCE, 0x5C, (byte) 0xF0, 0x00 + }; // 2524608000000 + // msec + public static final byte[] febMonthLeapMSec = { + 0, 0, 0, 0, (byte) 0x95, 0x58, 0x6C, 0x00 + }; // 2505600000 + public static final byte[] febMonthMsec = { + 0, 0, 0, 0, (byte) 0x90, 0x32, 0x10, 0x00 + }; // 2419200000 + public static final byte[] ThirtyOneDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9F, (byte) 0xA5, 0x24, 0x00 + }; // 2678400000 + public static final byte[] ThirtDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9A, 0x7E, (byte) 0xC8, 0x00 + }; // 2592000000 + public static final short year2051 = 2051; + public static final short year2020 = 2020; + // Convert to milliseconds constants + public static final byte[] SEC_TO_MILLIS_SHIFT_POS = {9, 8, 7, 6, 5, 3}; + + // -------------------------------------- + public static short convertToDate(short time, byte[] scratchPad, boolean utcFlag) { + + short yrsCount = 0; + short monthCount = 1; + short dayCount = 1; + short hhCount = 0; + short mmCount = 0; + short ssCount = 0; + byte Z = 0x5A; + boolean from2020 = true; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(time).length()), + KMInteger.cast(time).length()); + // If the time is less then 1 Jan 2020 then it is an error + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2020, (short) 0, (short) 8) + < 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (utcFlag + && KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + >= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + < 0) { + Util.arrayCopyNonAtomic(firstJan2020, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + from2020 = false; + Util.arrayCopyNonAtomic(firstJan2050, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + // divide the given time with four yrs msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, fourYrsMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(fourYrsMsec, (short) 0, scratchPad, (short) 8, (short) 8); + // quotient is multiple of 4 + yrsCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + yrsCount = (short) (yrsCount * 4); // number of yrs. + // copy reminder as new dividend + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Get the leap year index starting from the (base Year + yrsCount) Year. + short leapYrIdx = getLeapYrIndex(from2020, yrsCount); + + // if leap year index is 0, then the number of days for the 1st year will be 366 days. + // if leap year index is not 0, then the number of days for the 1st year will be 365 days. + if (((leapYrIdx == 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + >= 0)) + || ((leapYrIdx != 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + >= 0))) { + for (short i = 0; i < 4; i++) { + yrsCount++; + if (i == leapYrIdx) { + Util.arrayCopyNonAtomic(leapYearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + Util.arrayCopyNonAtomic(yearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + if (((short) (i + 1) == leapYrIdx)) { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } else { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } + } + } + + // total yrs from 1970 + if (from2020) { + yrsCount = (short) (year2020 + yrsCount); + } else { + yrsCount = (short) (year2051 + yrsCount); + } + + // divide the given time with one month msec count + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, oneMonthMsec, (short) 0, (short) 8) + >= 0) { + for (short i = 0; i < 12; i++) { + if (i == 1) { + // Feb month + if (isLeapYear(yrsCount)) { + // Leap year 29 days + Util.arrayCopyNonAtomic(febMonthLeapMSec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 28 days + Util.arrayCopyNonAtomic(febMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + } else if (((i <= 6) && ((i % 2 == 0))) || ((i > 6) && ((i % 2 == 1)))) { + Util.arrayCopyNonAtomic( + ThirtyOneDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 30 Days + Util.arrayCopyNonAtomic(ThirtDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, scratchPad, (short) 8, (short) 8) + >= 0) { + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + break; + } + monthCount++; + } + } + + // divide the given time with one day msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneDayMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneDayMsec, (short) 0, scratchPad, (short) 8, (short) 8); + dayCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + dayCount++; + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one hour msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneHourMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneHourMsec, (short) 0, scratchPad, (short) 8, (short) 8); + hhCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one minute msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneMinMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneMinMsec, (short) 0, scratchPad, (short) 8, (short) 8); + mmCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one second msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneSecMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneSecMsec, (short) 0, scratchPad, (short) 8, (short) 8); + ssCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Now convert to ascii string YYMMDDhhmmssZ or YYYYMMDDhhmmssZ + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = numberToString(yrsCount, scratchPad, (short) 0); // returns YYYY + len += numberToString(monthCount, scratchPad, len); + len += numberToString(dayCount, scratchPad, len); + len += numberToString(hhCount, scratchPad, len); + len += numberToString(mmCount, scratchPad, len); + len += numberToString(ssCount, scratchPad, len); + scratchPad[len] = Z; + len++; + if (utcFlag) { + return KMByteBlob.instance(scratchPad, (short) 2, (short) (len - 2)); // YY + } else { + return KMByteBlob.instance(scratchPad, (short) 0, len); // YYYY + } + } + + public static short numberToString(short number, byte[] scratchPad, short offset) { + byte zero = 0x30; + byte len = 2; + byte digit; + if (number > 999) { + len = 4; + } + byte index = len; + while (index > 0) { + digit = (byte) (number % 10); + number = (short) (number / 10); + scratchPad[(short) (offset + index - 1)] = (byte) (digit + zero); + index--; + } + return len; + } + + // Use Euclid's formula: dividend = quotient*divisor + remainder + // i.e. dividend - quotient*divisor = remainder where remainder < divisor. + // so this is division by subtraction until remainder remains. + public static short divide(byte[] buf, short dividend, short divisor, short remainder) { + short expCnt = 1; + short q = 0; + // first increase divisor so that it becomes greater then dividend. + while (compare(buf, divisor, dividend) < 0) { + shiftLeft(buf, divisor); + expCnt = (short) (expCnt << 1); + } + // Now subtract divisor from dividend if dividend is greater then divisor. + // Copy remainder in the dividend and repeat. + while (expCnt != 0) { + if (compare(buf, dividend, divisor) >= 0) { + subtract(buf, dividend, divisor, remainder, (byte) 8); + copy(buf, remainder, dividend); + q = (short) (q + expCnt); + } + expCnt = (short) (expCnt >> 1); + shiftRight(buf, divisor); + } + return q; + } + + public static void copy(byte[] buf, short from, short to) { + Util.arrayCopyNonAtomic(buf, from, buf, to, (short) 8); + } + + public static byte compare(byte[] buf, short lhs, short rhs) { + return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); + } + + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + + public static void shiftLeft(byte[] buf, short start) { + byte index = 7; + byte carry = 0; + byte tmp; + while (index >= 0) { + tmp = buf[(short) (start + index)]; + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] << 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] + carry); + if (tmp < 0) { + carry = 1; + } else { + carry = 0; + } + index--; + } + } + + public static void shiftRight(byte[] buf, short start) { + byte index = 0; + byte carry = 0; + byte tmp; + while (index < 8) { + tmp = (byte) (buf[(short) (start + index)] & 0x01); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] >> 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] & 0x7F); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] | carry); + if (tmp == 1) { + carry = (byte) 0x80; + } else { + carry = 0; + } + index++; + } + } + + public static void add(byte[] buf, short op1, short op2, short result) { + byte index = 7; + byte carry = 0; + short tmp; + short val1 = 0; + short val2 = 0; + while (index >= 0) { + val1 = (short) (buf[(short) (op1 + index)] & 0x00FF); + val2 = (short) (buf[(short) (op2 + index)] & 0x00FF); + tmp = (short) (val1 + val2 + carry); + carry = 0; + if (tmp > 255) { + carry = 1; // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); + index--; + } + } + + // subtraction by borrowing. + public static void subtract(byte[] buf, short op1, short op2, short result, byte sizeBytes) { + byte borrow = 0; + byte index = (byte) (sizeBytes - 1); + short r; + short x; + short y; + while (index >= 0) { + x = (short) (buf[(short) (op1 + index)] & 0xFF); + y = (short) (buf[(short) (op2 + index)] & 0xFF); + r = (short) (x - y - borrow); + borrow = 0; + if (r < 0) { + borrow = 1; + r = (short) (r + 256); // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (r & 0xFF); + index--; + } + } + + public static short countTemporalCount( + byte[] bufTime, short timeOff, short timeLen, byte[] scratchPad, short offset) { + Util.arrayFillNonAtomic(scratchPad, (short) offset, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic(bufTime, timeOff, scratchPad, (short) (offset + 8 - timeLen), timeLen); + Util.arrayCopyNonAtomic( + ThirtDaysMonthMsec, (short) 0, scratchPad, (short) (offset + 8), (short) 8); + return divide(scratchPad, (short) 0, (short) 8, (short) 16); + } + + public static boolean isLeapYear(short year) { + if ((short) (year % 4) == (short) 0) { + if (((short) (year % 100) == (short) 0) && ((short) (year % 400)) != (short) 0) { + return false; + } + return true; + } + return false; + } + + public static short getLeapYrIndex(boolean from2020, short yrsCount) { + short newBaseYr = (short) (from2020 ? (year2020 + yrsCount) : (year2051 + yrsCount)); + for (short i = 0; i < 4; i++) { + if (isLeapYear((short) (newBaseYr + i))) { + return i; + } + } + return -1; + } + + public static void computeOnesCompliment(byte[] buf, short offset, short len) { + short index = offset; + // Compute 1s compliment + while (index < (short) (len + offset)) { + buf[index] = (byte) ~buf[index]; + index++; + } + } + + // i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds( + byte[] buf, short inputOff, short outputOff, short scratchPadOff) { + short index = 0; + short length = (short) SEC_TO_MILLIS_SHIFT_POS.length; + while (index < length) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, SEC_TO_MILLIS_SHIFT_POS[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java new file mode 100644 index 0000000..06f1eaf --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java @@ -0,0 +1,47 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.AESKey; +import org.globalplatform.upgrade.Element; + +public class KMAESKey implements KMMasterKey { + + public AESKey aesKey; + + public KMAESKey(AESKey key) { + aesKey = key; + } + + public static void onSave(Element element, KMAESKey kmKey) { + element.write(kmKey.aesKey); + } + + public static KMAESKey onRestore(AESKey aesKey) { + if (aesKey == null) { + return null; + } + return new KMAESKey(aesKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java new file mode 100644 index 0000000..a3198dd --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -0,0 +1,1574 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.UpgradeManager; + +public class KMAndroidSEProvider implements KMSEProvider { + + public static final byte AES_GCM_TAG_LENGTH = 16; + public static final byte AES_GCM_NONCE_LENGTH = 12; + public static final byte KEYSIZE_128_OFFSET = 0x00; + public static final byte KEYSIZE_256_OFFSET = 0x01; + public static final short TMP_ARRAY_SIZE = 300; + private static final short RSA_KEY_SIZE = 256; + public static final short CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. + public static final byte SHARED_SECRET_KEY_SIZE = 32; + public static final byte POWER_RESET_FALSE = (byte) 0xAA; + public static final byte POWER_RESET_TRUE = (byte) 0x00; + private static final byte COMPUTED_HMAC_KEY_SIZE = 32; + private static byte[] CMAC_KDF_CONSTANT_L; + private static byte[] CMAC_KDF_CONSTANT_ZERO; + + private static KeyAgreement keyAgreement; + + // AESKey + private AESKey aesKeys[]; + // DES3Key + private DESKey triDesKey; + // HMACKey + private HMACKey hmacKey; + // RSA Key Pair + private KeyPair rsaKeyPair; + // EC Key Pair. + private KeyPair ecKeyPair; + // Temporary array. + public byte[] tmpArray; + // This is used for internal encryption/decryption operations. + private static AEADCipher aesGcmCipher; + + private Signature kdf; + public static byte[] resetFlag; + + private Signature hmacSignature; + // For ImportwrappedKey operations. + private KMRsaOAEPEncoding rsaOaepDecipher; + private KMPoolManager poolMgr; + + private KMOperationImpl globalOperation; + // Entropy + private RandomData rng; + + private static KMAndroidSEProvider androidSEProvider = null; + + public static KMAndroidSEProvider getInstance() { + return androidSEProvider; + } + + public KMAndroidSEProvider() { + initStatics(); + // Re-usable AES,DES and HMAC keys in persisted memory. + aesKeys = new AESKey[2]; + aesKeys[KEYSIZE_128_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + aesKeys[KEYSIZE_256_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + triDesKey = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + hmacKey = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); + poolMgr = KMPoolManager.getInstance(); + poolMgr.initECKey(ecKeyPair); + // RsaOAEP Decipher + rsaOaepDecipher = new KMRsaOAEPEncoding(KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1); + + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + + globalOperation = new KMOperationImpl(); + + // Temporary transient array created to use locally inside functions. + tmpArray = JCSystem.makeTransientByteArray(TMP_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + // Random number generator initialisation. + rng = RandomData.getInstance(RandomData.ALG_KEYGENERATION); + androidSEProvider = this; + resetFlag = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET); + resetFlag[0] = (byte) POWER_RESET_FALSE; + } + + void initStatics() { + CMAC_KDF_CONSTANT_L = new byte[] {0x00, 0x00, 0x01, 0x00}; + CMAC_KDF_CONSTANT_ZERO = new byte[] {0x00}; + } + + public void clean() { + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + } + + public AESKey createAESKey(short keysize) { + try { + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createAESKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public AESKey createAESKey(byte[] buf, short startOff, short length) { + AESKey key = null; + short keysize = (short) (length * 8); + if (keysize == 128) { + key = (AESKey) aesKeys[KEYSIZE_128_OFFSET]; + key.setKey(buf, (short) startOff); + } else if (keysize == 256) { + key = (AESKey) aesKeys[KEYSIZE_256_OFFSET]; + key.setKey(buf, (short) startOff); + } + return key; + } + + public DESKey createTDESKey() { + try { + newRandomNumber(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + return createTDESKey(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + } finally { + clean(); + } + } + + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + public HMACKey createHMACKey(short keysize) { + if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + try { + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createHMACKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + hmacKey.setKey(secretBuffer, secretOff, secretLength); + return hmacKey; + } + + public KeyPair createRsaKeyPair() { + rsaKeyPair.genKeyPair(); + return rsaKeyPair; + } + + public RSAPrivateKey createRsaKey( + byte[] modBuffer, + short modOff, + short modLength, + byte[] privBuffer, + short privOff, + short privLength) { + RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + } + + public KeyPair createECKeyPair() { + ecKeyPair.genKeyPair(); + return ecKeyPair; + } + + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + privKey.setS(privBuffer, privOff, privLength); + return privKey; + } + + @Override + public short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + switch (alg) { + case KMType.AES: + AESKey aesKey = createAESKey(keysize); + return aesKey.getKey(buf, startOff); + case KMType.DES: + DESKey desKey = createTDESKey(); + return desKey.getKey(buf, startOff); + case KMType.HMAC: + HMACKey hmacKey = createHMACKey(keysize); + return hmacKey.getKey(buf, startOff); + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return 0; + } + + @Override + public void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength, + short[] lengths) { + switch (alg) { + case KMType.RSA: + if (RSA_KEY_SIZE != privKeyLength || RSA_KEY_SIZE != pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KeyPair rsaKey = createRsaKeyPair(); + RSAPrivateKey privKey = (RSAPrivateKey) rsaKey.getPrivate(); + // Copy exponent. + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[0] = privKey.getExponent(tmpArray, (short) 0); + if (lengths[0] > privKeyLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + privKeyBuf, + (short) (privKeyStart + privKeyLength - lengths[0]), + lengths[0]); + // Copy modulus + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[1] = privKey.getModulus(tmpArray, (short) 0); + if (lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + pubModBuf, + (short) (pubModStart + pubModLength - lengths[1]), + lengths[1]); + break; + case KMType.EC: + KeyPair ecKey = createECKeyPair(); + ECPublicKey ecPubKey = (ECPublicKey) ecKey.getPublic(); + ECPrivateKey ecPrivKey = (ECPrivateKey) ecKey.getPrivate(); + lengths[0] = ecPrivKey.getS(privKeyBuf, privKeyStart); + lengths[1] = ecPubKey.getW(pubModBuf, pubModStart); + if (lengths[0] > privKeyLength || lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public boolean importSymmetricKey( + byte alg, short keysize, byte[] buf, short startOff, short length) { + switch (alg) { + case KMType.AES: + createAESKey(buf, startOff, length); + break; + case KMType.DES: + createTDESKey(buf, startOff, length); + break; + case KMType.HMAC: + createHMACKey(buf, startOff, length); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + switch (alg) { + case KMType.RSA: + createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, privKeyStart, privKeyLength); + break; + case KMType.EC: + createEcKey(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public void getTrueRandomNumber(byte[] buf, short start, short length) { + newRandomNumber(buf, start, length); + } + + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + rng.nextBytes(num, startOff, length); + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + rng.setSeed(num, offset, length); + } + + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (authTagLen != AES_GCM_TAG_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (nonceLen != AES_GCM_NONCE_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart); + aesGcmCipher.retrieveTag(authTag, authTagStart, authTagLen); + return ciphLen; + } + + @Override + public short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + return aesGCMEncrypt( + key, + secret, + secretStart, + secretLen, + encSecret, + encSecretStart, + nonce, + nonceStart, + nonceLen, + authData, + authDataStart, + authDataLen, + authTag, + authTagStart, + authTagLen); + } + + @Override + public boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + boolean verification = false; + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + // encrypt the secret + aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart); + verification = + aesGcmCipher.verifyTag( + authTag, authTagStart, (short) authTagLen, (short) AES_GCM_TAG_LENGTH); + return verification; + } + + public HMACKey cmacKdf( + KMPreSharedKey preSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength) { + try { + // This is hardcoded to requirement - 32 byte output with two concatenated + // 16 bytes K1 and K2. + final byte n = 2; // hardcoded + + // [i] counter - 32 bits + short iBufLen = 4; + short keyOutLen = n * 16; + // Convert Hmackey to AES Key as the algorithm is ALG_AES_CMAC_128. + KMHmacKey hmacKey = ((KMHmacKey) preSharedKey); + hmacKey.hmacKey.getKey(tmpArray, (short) 0); + aesKeys[KEYSIZE_256_OFFSET].setKey(tmpArray, (short) 0); + // Initialize the key derivation function. + kdf.init(aesKeys[KEYSIZE_256_OFFSET], Signature.MODE_SIGN); + // Clear the tmpArray buffer. + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + + Util.arrayFillNonAtomic(tmpArray, (short) 0, iBufLen, (byte) 0); + Util.arrayFillNonAtomic(tmpArray, (short) iBufLen, keyOutLen, (byte) 0); + + byte i = 1; + short pos = 0; + while (i <= n) { + tmpArray[3] = i; + // 4 bytes of iBuf with counter in it + kdf.update(tmpArray, (short) 0, (short) iBufLen); + kdf.update(label, labelStart, (short) labelLen); // label + kdf.update( + CMAC_KDF_CONSTANT_ZERO, + (short) 0, + (short) CMAC_KDF_CONSTANT_ZERO.length); // 1 byte of 0x00 + kdf.update(context, contextStart, contextLength); // context + // 4 bytes of L - signature of 16 bytes + pos = + kdf.sign( + CMAC_KDF_CONSTANT_L, + (short) 0, + (short) CMAC_KDF_CONSTANT_L.length, + tmpArray, + (short) (iBufLen + pos)); + i++; + } + return createHMACKey(tmpArray, (short) iBufLen, (short) keyOutLen); + } finally { + clean(); + } + } + + public short hmacSign( + HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + hmacSignature.init(key, Signature.MODE_SIGN); + return hmacSignature.sign(data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart) { + HMACKey key = createHMACKey(keyBuf, keyStart, keyLength); + return hmacSign(key, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + Object key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + if (!(key instanceof KMHmacKey)) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + KMHmacKey hmacKey = (KMHmacKey) key; + return hmacSign(hmacKey.hmacKey, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacKDF( + KMMasterKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + try { + KMAESKey aesKey = (KMAESKey) masterkey; + short keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + return hmacSign( + tmpArray, (short) 0, keyLen, data, dataStart, dataLength, signature, signatureStart); + } finally { + clean(); + } + } + + @Override + public boolean hmacVerify( + KMComputedHmacKey key, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart, + short macLength) { + KMHmacKey hmacKey = (KMHmacKey) key; + hmacSignature.init(hmacKey.hmacKey, Signature.MODE_VERIFY); + return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength); + } + + @Override + public short rsaDecipherOAEP256( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + key.setExponent(secret, (short) secretStart, (short) secretLength); + key.setModulus(modBuffer, (short) modOff, (short) modLength); + rsaOaepDecipher.init(key, Cipher.MODE_DECRYPT); + return rsaOaepDecipher.doFinal( + inputDataBuf, + (short) inputDataStart, + (short) inputDataLength, + outputDataBuf, + (short) outputDataStart); + } + + private byte mapSignature256Alg(byte alg, byte padding, byte digest) { + switch (alg) { + case KMType.RSA: + switch (padding) { + case KMType.RSA_PKCS1_1_5_SIGN: + { + if (digest == KMType.DIGEST_NONE) { + return KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST; + } else { + return Signature.ALG_RSA_SHA_256_PKCS1; + } + } + case KMType.RSA_PSS: + return Signature.ALG_RSA_SHA_256_PKCS1_PSS; + case KMType.PADDING_NONE: + return KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD; + } + break; + case KMType.EC: + if (digest == KMType.DIGEST_NONE) { + return KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST; + } else { + return Signature.ALG_ECDSA_SHA_256; + } + case KMType.HMAC: + return Signature.ALG_HMAC_SHA_256; + } + return -1; + } + + private byte mapCipherAlg(byte alg, byte padding, byte blockmode, byte digest) { + switch (alg) { + case KMType.AES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; + case KMType.CTR: + return Cipher.ALG_AES_CTR; + case KMType.GCM: + return AEADCipher.ALG_AES_GCM; + } + break; + case KMType.DES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_DES_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_DES_CBC_NOPAD; + } + break; + case KMType.RSA: + switch (padding) { + case KMType.PADDING_NONE: + return Cipher.ALG_RSA_NOPAD; + case KMType.RSA_PKCS1_1_5_ENCRYPT: + return Cipher.ALG_RSA_PKCS1; + case KMType.RSA_OAEP: + { + if (digest == KMType.SHA1) { + /* MGF Digest is SHA1 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1; + } else if (digest == KMType.SHA2_256) { + /* MGF Digest is SHA256 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256; + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + break; + } + return -1; + } + + public KMOperation createSymmetricCipher( + short alg, + short purpose, + short macLength, + short blockMode, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] ivBuffer, + short ivStart, + short ivLength, + boolean isRkp) { + + short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode, (byte) 0); + KMOperation operation = null; + if (isRkp) { + operation = poolMgr.getRKpOperation(purpose, cipherAlg, alg, padding, blockMode, macLength); + } else { + operation = + poolMgr.getOperationImpl( + purpose, cipherAlg, alg, padding, blockMode, macLength, secretLength, false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + Key key = (Key) keyObj.keyObjectInst; + switch (secretLength) { + case 32: + case 16: + ((AESKey) key).setKey(secret, secretStart); + break; + case 24: + ((DESKey) key).setKey(secret, secretStart); + break; + default: + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + break; + } + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, ivBuffer, ivStart, ivLength); + return operation; + } + + public KMOperation createHmacSignerVerifier( + short purpose, + short digest, + byte[] secret, + short secretStart, + short secretLength, + boolean isRkp) { + KMOperation operation = null; + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (isRkp) { + operation = + poolMgr.getRKpOperation( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + key.setKey(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + private KMOperation createHmacSignerVerifier( + short purpose, short digest, HMACKey hmacKey, boolean isTrustedConf) { + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KMOperation operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + isTrustedConf); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + short len = hmacKey.getKey(tmpArray, (short) 0); + key.setKey(tmpArray, (short) 0, len); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + true /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, true /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + case KMType.DES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + false /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, false /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot) { + short keyLen = 0; + globalOperation.setPurpose(purpose); + globalOperation.setAlgorithmType(alg); + globalOperation.setPaddingAlgorithm(padding); + globalOperation.setBlockMode(blockMode); + try { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey aesKey = (KMAESKey) key; + keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + switch (alg) { + case KMType.HMAC: + HMACKey hmackey = createHMACKey(tmpArray, (short) 0, keyLen); + globalOperation.setSignature(hmacSignature); + globalOperation.init(hmackey, digest, null, (short) 0, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } finally { + clean(); + } + return globalOperation; + } + + @Override + public KMOperation initTrustedConfirmationSymmetricOperation(KMComputedHmacKey computedHmacKey) { + KMHmacKey key = (KMHmacKey) computedHmacKey; + return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true); + } + + public KMOperation createRsaSigner( + short digest, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte alg = mapSignature256Alg(KMType.RSA, (byte) padding, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createRsaDecipher( + short padding, + short mgfDigest, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0, (byte) mgfDigest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.DECRYPT, + cipherAlg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createEcSigner( + short digest, byte[] secret, short secretStart, short secretLength) { + byte alg = mapSignature256Alg(KMType.EC, (byte) 0, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createKeyAgreement(byte[] secret, short secretStart, short secretLength) { + KMOperation operation = + poolMgr.getOperationImpl( + KMType.AGREE_KEY, + KeyAgreement.ALG_EC_SVDP_DH_PLAIN, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + KMOperation opr = null; + if (alg == KMType.RSA) { + switch (purpose) { + case KMType.SIGN: + opr = + createRsaSigner( + digest, + padding, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + case KMType.DECRYPT: + opr = + createRsaDecipher( + padding, + mgfDigest, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else if (alg == KMType.EC) { + switch (purpose) { + case KMType.SIGN: + opr = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength); + break; + + case KMType.AGREE_KEY: + opr = createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return opr; + } + + @Override + public short cmacKDF( + KMPreSharedKey pSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] keyBuf, + short keyStart) { + HMACKey key = + cmacKdf(pSharedKey, label, labelStart, labelLen, context, contextStart, contextLength); + return key.getKey(keyBuf, keyStart); + } + + @Override + public boolean isUpgrading() { + return UpgradeManager.isUpgrading(); + } + + @Override + public KMMasterKey createMasterKey(KMMasterKey masterKey, short keySizeBits) { + try { + if (masterKey == null) { + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false); + masterKey = new KMAESKey(key); + short keyLen = (short) (keySizeBits / 8); + getTrueRandomNumber(tmpArray, (short) 0, keyLen); + ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); + } + return (KMMasterKey) masterKey; + } finally { + clean(); + } + } + + @Override + public KMPreSharedKey createPreSharedKey( + KMPreSharedKey preSharedKey, byte[] keyData, short offset, short length) { + short lengthInBits = (short) (length * 8); + if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (preSharedKey == null) { + HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, lengthInBits, false); + preSharedKey = new KMHmacKey(key); + } + ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length); + return (KMPreSharedKey) preSharedKey; + } + + @Override + public KMComputedHmacKey createComputedHmacKey( + KMComputedHmacKey computedHmacKey, byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (computedHmacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + computedHmacKey = new KMHmacKey(key); + } + ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length); + return (KMComputedHmacKey) computedHmacKey; + } + + @Override + public short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + ECPrivateKey key = (ECPrivateKey) ecKeyPair.getPrivate(); + key.setS(secret, secretStart, secretLength); + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short ecSign256( + KMAttestationKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(((KMECPrivateKey) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_RSA, Cipher.PAD_PKCS1); + + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + ; + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuf, modStart, modLength); + + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public boolean isAttestationKeyProvisioned() { + return false; + } + + @Override + public short getAttestationKeyAlgorithm() { + return KMType.INVALID_VALUE; + } + + @Override + public short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // HMAC_extract + hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, tmpArray, (short) 0); + // HMAC_expand + return hkdfExpand(tmpArray, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen); + } + + private short hkdfExtract( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] out, + short off) { + // https://tools.ietf.org/html/rfc5869#section-2.2 + HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen); + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off); + } + + private short hkdfExpand( + byte[] prk, + short prkOff, + short prkLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // https://tools.ietf.org/html/rfc5869#section-2.3 + short digestLen = (short) 32; // SHA256 digest length. + // Calculate no of iterations N. + short n = (short) ((short) (outLen + digestLen - 1) / digestLen); + if (n > 255) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); + Util.arrayFill(tmpArray, (short) 0, (short) 33, (byte) 0); + short bytesCopied = 0; + short len = 0; + for (short i = 0; i < n; i++) { + tmpArray[0]++; + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + if (i != 0) { + hmacSignature.update(tmpArray, (short) 1, (short) 32); + } + hmacSignature.update(info, infoOff, infoLen); + len = hmacSignature.sign(tmpArray, (short) 0, (short) 1, tmpArray, (short) 1); + if ((short) (bytesCopied + len) > outLen) { + len = (short) (outLen - bytesCopied); + } + Util.arrayCopyNonAtomic(tmpArray, (short) 1, out, (short) (outOff + bytesCopied), len); + bytesCopied += len; + } + return outLen; + } + + @Override + public short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff) { + keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen)); + return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff); + } + + @Override + public boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic(); + key.setW(pubKey, pubKeyOffset, pubKeyLen); + signer.init(key, Signature.MODE_VERIFY); + return signer.verify( + inputDataBuf, + inputDataStart, + inputDataLength, + signatureDataBuf, + signatureDataStart, + (short) (signatureDataBuf[(short) (signatureDataStart + 1)] + 2)); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short ecSign256( + KMDeviceUniqueKeyPair ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(((KMECDeviceUniqueKey) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( + KMDeviceUniqueKeyPair key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (key == null) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + poolMgr.initECKey(ecKeyPair); + key = new KMECDeviceUniqueKey(ecKeyPair); + } + ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKey) key).ecKeyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKey) key).ecKeyPair.getPublic(); + ecKeyPair.setS(privKey, privKeyOff, privKeyLen); + ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen); + return (KMDeviceUniqueKeyPair) key; + } + + @Override + public KMRkpMacKey createRkpMacKey( + KMRkpMacKey rkpMacKey, byte[] keyData, short offset, short length) { + if (rkpMacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + rkpMacKey = new KMHmacKey(key); + } + ((KMHmacKey) rkpMacKey).hmacKey.setKey(keyData, offset, length); + return rkpMacKey; + } + + @Override + public short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) { + MessageDigest.OneShot mDigest = null; + short len = 0; + try { + mDigest = MessageDigest.OneShot.open(MessageDigest.ALG_SHA_256); + len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + } finally { + if (mDigest != null) { + mDigest.close(); + mDigest = null; + } + } + return len; + } + + public boolean isPowerReset() { + boolean flag = false; + if (resetFlag[0] == POWER_RESET_TRUE) { + resetFlag[0] = POWER_RESET_FALSE; + flag = true; + if (poolMgr != null) { + poolMgr.powerReset(); + } + } + return flag; + } + + @Override + public void onSave(Element element, byte interfaceType, Object object) { + element.write(interfaceType); + if (object == null) { + element.write(null); + return; + } + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey.onSave(element, (KMAESKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + KMECDeviceUniqueKey.onSave(element, (KMECDeviceUniqueKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + @Override + public Object onRestore(Element element) { + if (element == null) { + return null; + } + byte interfaceType = element.readByte(); + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_COMPUTED_HMAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.onRestore((AESKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKey.onRestore((KeyPair) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return null; + } + + @Override + public short getBackupPrimitiveByteCount(byte interfaceType) { + short primitiveCount = 1; // interface type + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + primitiveCount += KMAESKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + primitiveCount += KMECDeviceUniqueKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return primitiveCount; + } + + @Override + public short getBackupObjectCount(byte interfaceType) { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.getBackupObjectCount(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + @Override + public boolean isBootSignalEventSupported() { + return false; + } + + @Override + public boolean isDeviceRebooted() { + return false; + } + + @Override + public void clearDeviceBooted(boolean resetBootFlag) { + // To be filled + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java new file mode 100644 index 0000000..da60794 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java @@ -0,0 +1,198 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * The KMAttestationCert interface represents a X509 compliant attestation certificate required to + * support keymaster's attestKey function. This cert will be created according to the specifications + * given in android keymaster hal documentation. KMSeProvider has to provide the instance of this + * certificate. This interface is designed based on builder pattern and hence each method returns + * instance of cert. + */ +public interface KMAttestationCert { + + /** + * Set verified boot hash. + * + * @param obj This is a KMByteBlob containing hash + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootHash(short obj); + + /** + * Set verified boot key received during booting up. + * + * @param obj This is a KMByteBlob containing verified boot key. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootKey(short obj); + + /** + * Set verified boot state received during booting up. + * + * @param val This is a byte containing verified boot state value. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootState(byte val); + + /** + * Set uniqueId received from CA certificate during provisioning. + * + * @param scratchpad Buffer to store intermediate results. + * @param scratchPadOff Start offset of the scratchpad buffer. + * @param creationTime This buffer contains the CREATION_TIME value. + * @param creationTimeOff Start offset of creattionTime buffer. + * @param creationTimeLen Length of the creationTime buffer. + * @param attestAppId This buffer contains the ATTESTATION_APPLICATION_ID value. + * @param attestAppIdOff Start offset of the attestAppId buffer. + * @param attestAppIdLen Length of the attestAppId buffer. + * @param resetSinceIdRotation This holds the information of RESET_SINCE_ID_ROTATION. + * @param masterKey + * @return instance of KMAttestationCert. + */ + KMAttestationCert makeUniqueId( + byte[] scratchpad, + short scratchPadOff, + byte[] creationTime, + short creationTimeOff, + short creationTimeLen, + byte[] attestAppId, + short attestAppIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMMasterKey masterKey); + + /** + * Set start time received from creation/activation time tag. Used for certificate's valid period. + * + * @param obj This is a KMByteBlob object containing start time. + * @param scratchpad Buffer to store intermediate results. + * @return instance of KMAttestationCert. + */ + KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad); + + /** + * Set expiry time received from expiry time tag or ca certificates expiry time. Used for + * certificate's valid period. + * + * @param usageExpiryTimeObj This is a KMByteBlob containing expiry time. certificate. + * @param scratchPad Buffer to store intermediate results. + * @return instance of KMAttestationCert + */ + KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad); + + /** + * Set device lock status received during booting time or due to device lock command. + * + * @param val This is true if device is locked. + * @return instance of KMAttestationCert + */ + KMAttestationCert deviceLocked(boolean val); + + /** + * Set public key to be attested received from attestKey command. + * + * @param obj This is KMByteBlob containing the public key. + * @return instance of KMAttestationCert + */ + KMAttestationCert publicKey(short obj); + + /** + * Set attestation challenge received from attestKey command. + * + * @param obj This is KMByteBlob containing the attestation challenge. + * @return instance of KMAttestationCert + */ + KMAttestationCert attestationChallenge(short obj); + + /** + * Set extension tag received from key characteristics which needs to be added to android + * extension. This method will called once for each tag. + * + * @param tag is the KMByteBlob containing KMTag. + * @param hwEnforced is true if the tag has to be added to hw enforced list or else added to sw + * enforced list. + * @return instance of KMAttestationCert + */ + KMAttestationCert extensionTag(short tag, boolean hwEnforced); + + /** + * Set ASN.1 encoded X509 issuer field received from attestation key CA cert. + * + * @param obj This is KMByteBlob containing the issuer. + * @return instance of KMAttestationCert + */ + KMAttestationCert issuer(short obj); + + /** + * Set byte buffer to be used to generate certificate. + * + * @param buf This is byte[] buffer. + * @param bufStart This is short start offset. + * @param maxLen This is short length of the buffer. + * @return instance of KMAttestationCert + */ + KMAttestationCert buffer(byte[] buf, short bufStart, short maxLen); + + /** + * Get the start of the certificate + * + * @return start of the attestation cert. + */ + short getCertStart(); + + /** + * Get the length of the certificate + * + * @return length of the attestation cert. + */ + short getCertLength(); + + /** + * Build a fake signed certificate. After this method executes the certificate is ready with the + * signature equal to 1 byte which is 0 and with rsa signature algorithm. + */ + void build(); + + /** + * Set the Serial number in the certificate. If no serial number is set then serial number is 1. + * + * @param serialNumber + */ + boolean serialNumber(short serialNumber); + + /** + * Set the Subject Name in the certificate. + * + * @param subject + */ + boolean subjectName(short subject); + + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert ecAttestKey(short attestKey, byte mode); + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java new file mode 100644 index 0000000..d941306 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java @@ -0,0 +1,23 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMAttestationKey is a marker interface and the SE Provider has to implement this interface. + * Internally attestation key is stored as a Javacard EC key pair object, which will provide + * additional security. The attestation key is maintained by the SEProvider. + */ +public interface KMAttestationKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java new file mode 100644 index 0000000..8d406b4 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java @@ -0,0 +1,3 @@ +package com.android.javacard.seprovider; + +public interface KMComputedHmacKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java new file mode 100644 index 0000000..a1d6454 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java @@ -0,0 +1,11 @@ +package com.android.javacard.seprovider; + +public class KMDataStoreConstants { + // INTERFACE Types + public static final byte INTERFACE_TYPE_COMPUTED_HMAC_KEY = 0x01; + public static final byte INTERFACE_TYPE_ATTESTATION_KEY = 0x02; + public static final byte INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR = 0x03; + public static final byte INTERFACE_TYPE_MASTER_KEY = 0x04; + public static final byte INTERFACE_TYPE_PRE_SHARED_KEY = 0x05; + public static final byte INTERFACE_TYPE_RKP_MAC_KEY = 0x06; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java new file mode 100644 index 0000000..9bbccd8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java @@ -0,0 +1,21 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +public interface KMDeviceUniqueKeyPair { + + short getPublicKey(byte[] buf, short offset); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java new file mode 100644 index 0000000..8adb1fb --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java @@ -0,0 +1,54 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +public class KMECDeviceUniqueKey implements KMDeviceUniqueKeyPair { + + public KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKey(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECDeviceUniqueKey kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECDeviceUniqueKey onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECDeviceUniqueKey(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java new file mode 100644 index 0000000..35c687b --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java @@ -0,0 +1,47 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +public class KMECPrivateKey implements KMAttestationKey { + + public KeyPair ecKeyPair; + + public KMECPrivateKey(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECPrivateKey kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECPrivateKey onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECPrivateKey(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java new file mode 100644 index 0000000..e9b95ee --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java @@ -0,0 +1,132 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +public class KMEcdsa256NoDigestSignature extends Signature { + + public static final byte ALG_ECDSA_NODIGEST = (byte) 0x67; + public static final byte MAX_NO_DIGEST_MSG_LEN = 32; + private byte algorithm; + private Signature inst; + + public KMEcdsa256NoDigestSignature(byte alg) { + algorithm = alg; + inst = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return inst.getLength(); + } + + @Override + public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + try { + if (i1 > MAX_NO_DIGEST_MSG_LEN) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + // add zeros to the left + if (i1 < MAX_NO_DIGEST_MSG_LEN) { + Util.arrayFillNonAtomic( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + (byte) 0); + } + Util.arrayCopyNonAtomic( + bytes, + i, + KMAndroidSEProvider.getInstance().tmpArray, + (short) (MAX_NO_DIGEST_MSG_LEN - i1), + i1); + return inst.signPreComputedHash( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + bytes1, + i2); + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return inst.sign(bytes, i, i1, bytes1, i2); + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java new file mode 100644 index 0000000..69cb069 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java @@ -0,0 +1,32 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INVALID_INPUT_LENGTH = 21; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_ARGUMENT = 38; + public static final short UNKNOWN_ERROR = 1000; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java new file mode 100644 index 0000000..79983a2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; + +/** + * KMException is shared instance of exception used for all exceptions in the applet. It is used to + * throw EMError errors. + */ +public class KMException extends RuntimeException { + + private static short[] reason; + private static KMException exception; + + private KMException() {} + + public static short reason() { + return reason[0]; + } + + public static void throwIt(short e) { + if (reason == null) { + reason = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); + } + if (exception == null) { + exception = new KMException(); + } + reason[0] = e; + throw exception; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java new file mode 100644 index 0000000..3d25143 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java @@ -0,0 +1,47 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.HMACKey; +import org.globalplatform.upgrade.Element; + +public class KMHmacKey implements KMPreSharedKey, KMComputedHmacKey, KMRkpMacKey { + + public HMACKey hmacKey; + + public KMHmacKey(HMACKey key) { + hmacKey = key; + } + + public static void onSave(Element element, KMHmacKey kmKey) { + element.write(kmKey.hmacKey); + } + + public static KMHmacKey onRestore(HMACKey hmacKey) { + if (hmacKey == null) { + return null; + } + return new KMHmacKey(hmacKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java new file mode 100644 index 0000000..26edaa2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java @@ -0,0 +1,6 @@ +package com.android.javacard.seprovider; + +public class KMKeyObject { + public byte algorithm; + public Object keyObjectInst; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java new file mode 100644 index 0000000..27afb3d --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java @@ -0,0 +1,23 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMMasterKey is a marker interface and the SE Provider has to implement this interface. Internally + * Masterkey is stored as a Javacard AES key object, which will provide additional security. The + * master key is maintained by the SEProvider. + */ +public interface KMMasterKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java new file mode 100644 index 0000000..12e691e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java @@ -0,0 +1,75 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMOperation represents a persistent operation started by keymaster hal's beginOperation function. + * This operation is persistent i.e. it will be stored in non volatile memory of se card. It will be + * returned back to KMSEProvider for the reuse when the operation is finished. + */ +public interface KMOperation { + + // Used for cipher operations + short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for signature operations + short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength); + + // Used for finishing cipher operations or ecdh keyAgreement. + short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for finishing signing operations. + short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart); + + // Used for finishing verifying operations. + boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength); + + // Used for aborting the ongoing operations. + void abort(); + + // Used for AES GCM cipher operation. + void updateAAD(byte[] dataBuf, short dataStart, short dataLength); + + // Used for getting output size before finishing a AES GCM cipher operation. For encryption this + // will + // include the auth tag which is appended at the end of the encrypted data. For decryption this + // will be + // size of the decrypted data only. + short getAESGCMOutputSize(short dataSize, short macLength); + + KMKeyObject getKeyObject(); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java new file mode 100644 index 0000000..b2a2421 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -0,0 +1,411 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.PrivateKey; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +public class KMOperationImpl implements KMOperation { + + private static final byte ALG_TYPE_OFFSET = 0x00; + private static final byte PADDING_OFFSET = 0x01; + private static final byte PURPOSE_OFFSET = 0x02; + private static final byte BLOCK_MODE_OFFSET = 0x03; + private static final byte MAC_LENGTH_OFFSET = 0x04; + private final byte[] EMPTY = {}; + // This will hold the length of the buffer stored inside the + // Java Card after the GCM update operation. + private static final byte AES_GCM_UPDATE_LEN_OFFSET = 0x05; + private static final byte PARAMETERS_LENGTH = 6; + private short[] parameters; + // Either one of Cipher/Signature instance is stored. + private Object[] operationInst; + + public KMOperationImpl() { + parameters = JCSystem.makeTransientShortArray(PARAMETERS_LENGTH, JCSystem.CLEAR_ON_RESET); + operationInst = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public short getPurpose() { + return parameters[PURPOSE_OFFSET]; + } + + public void setPurpose(short mode) { + parameters[PURPOSE_OFFSET] = mode; + } + + public short getMacLength() { + return parameters[MAC_LENGTH_OFFSET]; + } + + public void setMacLength(short macLength) { + parameters[MAC_LENGTH_OFFSET] = macLength; + } + + public short getPaddingAlgorithm() { + return parameters[PADDING_OFFSET]; + } + + public void setPaddingAlgorithm(short alg) { + parameters[PADDING_OFFSET] = alg; + } + + public void setBlockMode(short mode) { + parameters[BLOCK_MODE_OFFSET] = mode; + } + + public short getBlockMode() { + return parameters[BLOCK_MODE_OFFSET]; + } + + public short getAlgorithmType() { + return parameters[ALG_TYPE_OFFSET]; + } + + public void setAlgorithmType(short cipherAlg) { + parameters[ALG_TYPE_OFFSET] = cipherAlg; + } + + public void setCipher(Cipher cipher) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = cipher; + } + + public void setSignature(Signature signer) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = signer; + } + + public void setKeyAgreement(KeyAgreement keyAgreement) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = keyAgreement; + } + + public boolean isResourceMatches(Object object, byte resourceType) { + return operationInst[resourceType] == object; + } + + public void setKeyObject(KMKeyObject keyObject) { + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = keyObject; + } + + public KMKeyObject getKeyObject() { + return (KMKeyObject) operationInst[KMPoolManager.RESOURCE_TYPE_KEY]; + } + + private void reset() { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = null; + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = null; + parameters[MAC_LENGTH_OFFSET] = KMType.INVALID_VALUE; + parameters[AES_GCM_UPDATE_LEN_OFFSET] = 0; + parameters[BLOCK_MODE_OFFSET] = KMType.INVALID_VALUE; + parameters[PURPOSE_OFFSET] = KMType.INVALID_VALUE; + parameters[ALG_TYPE_OFFSET] = KMType.INVALID_VALUE; + parameters[PADDING_OFFSET] = KMType.INVALID_VALUE; + } + + private byte mapPurpose(short purpose) { + switch (purpose) { + case KMType.ENCRYPT: + return Cipher.MODE_ENCRYPT; + case KMType.DECRYPT: + return Cipher.MODE_DECRYPT; + case KMType.SIGN: + return Signature.MODE_SIGN; + case KMType.VERIFY: + return Signature.MODE_VERIFY; + } + return -1; + } + + private void initSymmetricCipher(Key key, byte[] ivBuffer, short ivStart, short ivLength) { + Cipher symmCipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + byte cipherAlg = symmCipher.getAlgorithm(); + switch (cipherAlg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_CTR: + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + symmCipher.init(key, mapPurpose(getPurpose())); + break; + case Cipher.ALG_DES_CBC_NOPAD: + // Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + // While sending back the iv, send only 8 bytes. + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, (short) 8); + break; + case AEADCipher.ALG_AES_GCM: + ((AEADCipher) symmCipher).init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + private void initRsa(Key key, short digest) { + if (KMType.SIGN == getPurpose()) { + byte mode; + if (getPaddingAlgorithm() == KMType.PADDING_NONE + || (getPaddingAlgorithm() == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { + mode = Cipher.MODE_DECRYPT; + } else { + mode = Signature.MODE_SIGN; + } + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key, mode); + } else { // RSA Cipher + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + private void initEc(Key key) { + if (KMType.AGREE_KEY == getPurpose()) { + ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key); + } else { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + public void init(Key key, short digest, byte[] buf, short start, short length) { + switch (getAlgorithmType()) { + case KMType.AES: + case KMType.DES: + initSymmetricCipher(key, buf, start, length); + break; + case KMType.HMAC: + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init(key, mapPurpose(getPurpose())); + break; + case KMType.RSA: + initRsa(key, digest); + break; + case KMType.EC: + initEc(key); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + short len = + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (parameters[ALG_TYPE_OFFSET] == KMType.AES && parameters[BLOCK_MODE_OFFSET] == KMType.GCM) { + // Every time Block size data is stored as intermediate result. + parameters[AES_GCM_UPDATE_LEN_OFFSET] += (short) (inputDataLength - len); + } + return len; + } + + @Override + public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength); + return 0; + } + + private short finishKeyAgreement( + byte[] publicKey, short start, short len, byte[] output, short outputStart) { + return ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .generateSecret(publicKey, start, len, output, outputStart); + } + + private short finishCipher( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + short len = 0; + try { + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + Cipher cipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + short cipherAlg = parameters[ALG_TYPE_OFFSET]; + short blockMode = parameters[BLOCK_MODE_OFFSET]; + short mode = parameters[PURPOSE_OFFSET]; + short macLength = parameters[MAC_LENGTH_OFFSET]; + short padding = parameters[PADDING_OFFSET]; + + if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.DECRYPT) { + inputDataLen = (short) (inputDataLen - macLength); + } + } else if ((cipherAlg == KMType.DES || cipherAlg == KMType.AES) + && padding == KMType.PKCS7 + && mode == KMType.ENCRYPT) { + byte blkSize = 16; + byte paddingBytes; + short inputlen = inputDataLen; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + // padding bytes + if (inputlen % blkSize == 0) { + paddingBytes = blkSize; + } else { + paddingBytes = (byte) (blkSize - (inputlen % blkSize)); + } + // final len with padding + inputlen = (short) (inputlen + paddingBytes); + // intermediate buffer to copy input data+padding + // fill in the padding + Util.arrayFillNonAtomic(tmpArray, (short) 0, inputlen, paddingBytes); + // copy the input data + Util.arrayCopyNonAtomic(inputDataBuf, inputDataStart, tmpArray, (short) 0, inputDataLen); + inputDataBuf = tmpArray; + inputDataLen = inputlen; + inputDataStart = 0; + } + len = + cipher.doFinal( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + if ((cipherAlg == KMType.AES || cipherAlg == KMType.DES) + && padding == KMType.PKCS7 + && mode == KMType.DECRYPT) { + byte blkSize = 16; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + if (len > 0) { + // verify if padding is corrupted. + byte paddingByte = outputDataBuf[(short) (outputDataStart + len - 1)]; + // padding byte always should be <= block size + if ((short) paddingByte > blkSize || (short) paddingByte <= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + for (short j = 1; j <= paddingByte; ++j) { + if (outputDataBuf[(short) (outputDataStart + len - j)] != paddingByte) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + len = (short) (len - (short) paddingByte); // remove the padding bytes + } + } else if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.ENCRYPT) { + len += + ((AEADCipher) cipher) + .retrieveTag(outputDataBuf, (short) (outputDataStart + len), macLength); + } else { + boolean verified = + ((AEADCipher) cipher) + .verifyTag( + inputDataBuf, (short) (inputDataStart + inputDataLen), macLength, macLength); + if (!verified) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + } + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + return len; + } + + @Override + public short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + if (parameters[PURPOSE_OFFSET] == KMType.AGREE_KEY) { + return finishKeyAgreement( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } else { + return finishCipher( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } + } + + @Override + public short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); + } + + @Override + public boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); + } + + @Override + public void abort() { + // Few simulators does not reset the Hmac signer instance on init so as + // a workaround to reset the hmac signer instance in case of abort/failure of the operation + // the corresponding sign / verify function is called. + if (operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] != null) { + if ((parameters[PURPOSE_OFFSET] == KMType.SIGN || parameters[PURPOSE_OFFSET] == KMType.VERIFY) + && (((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).getAlgorithm() + == Signature.ALG_HMAC_SHA_256)) { + Signature signer = (Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + try { + if (parameters[PURPOSE_OFFSET] == KMType.SIGN) { + signer.sign(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0); + } else { + signer.verify(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0, (short) 0); + } + } catch (Exception e) { + // Ignore. + } + } + } + reset(); + } + + @Override + public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { + ((AEADCipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .updateAAD(dataBuf, dataStart, dataLength); + } + + @Override + public short getAESGCMOutputSize(short dataSize, short macLength) { + if (parameters[PURPOSE_OFFSET] == KMType.ENCRYPT) { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize + macLength); + } else { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize - macLength); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java new file mode 100644 index 0000000..fa32c70 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -0,0 +1,657 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** This class manages all the pool instances. */ +public class KMPoolManager { + + public static final byte MAX_OPERATION_INSTANCES = 4; + private static final byte HMAC_MAX_OPERATION_INSTANCES = 8; + public static final byte AES_128 = 0x04; + public static final byte AES_256 = 0x05; + // Resource type constants + public static final byte RESOURCE_TYPE_CRYPTO = 0x00; + public static final byte RESOURCE_TYPE_KEY = 0x01; + // static final variables + // -------------------------------------------------------------- + // P-256 Curve Parameters + static byte[] secp256r1_P; + static byte[] secp256r1_A; + + static byte[] secp256r1_B; + static byte[] secp256r1_S; + + // Uncompressed form + static byte[] secp256r1_UCG; + static byte[] secp256r1_N; + static final short secp256r1_H = 1; + // -------------------------------------------------------------- + + // Cipher pool + private Object[] cipherPool; + // Signature pool + private Object[] signerPool; + // Keyagreement pool + private Object[] keyAgreementPool; + // KMOperationImpl pool + private Object[] operationPool; + // Hmac signer pool which is used to support TRUSTED_CONFIRMATION_REQUIRED tag. + private Object[] hmacSignOperationPool; + + private Object[] keysPool; + // RKP uses AESGCM and HMAC in generateCSR flow. + KMOperation rkpOPeration; + Cipher rkpAesGcm; + Signature rkpHmac; + KMKeyObject rkpHmacKey; + KMKeyObject rkpAesKey; + + final byte[] KEY_ALGS = { + AES_128, AES_256, KMType.DES, KMType.RSA, KMType.EC, KMType.HMAC, + }; + + final byte[] CIPHER_ALGS = { + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, + Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, + Cipher.ALG_DES_CBC_NOPAD, + Cipher.ALG_DES_ECB_NOPAD, + Cipher.ALG_AES_CTR, + Cipher.ALG_RSA_PKCS1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256, + Cipher.ALG_RSA_NOPAD, + AEADCipher.ALG_AES_GCM + }; + + final byte[] SIG_ALGS = { + Signature.ALG_RSA_SHA_256_PKCS1, + Signature.ALG_RSA_SHA_256_PKCS1_PSS, + Signature.ALG_ECDSA_SHA_256, + Signature.ALG_HMAC_SHA_256, + KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD, + KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST, + KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST + }; + + final byte[] KEY_AGREE_ALGS = {KeyAgreement.ALG_EC_SVDP_DH_PLAIN}; + + private static KMPoolManager poolManager; + + public static KMPoolManager getInstance() { + if (poolManager == null) { + poolManager = new KMPoolManager(); + } + return poolManager; + } + + public static void initStatics() { + secp256r1_P = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF + }; + + secp256r1_A = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFC + }; + + secp256r1_B = + new byte[] { + (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, + (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, + (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, + (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, + (byte) 0x60, (byte) 0x4B + }; + + secp256r1_S = + new byte[] { + (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, + (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, + (byte) 0x7E, (byte) 0x90 + }; + + // Uncompressed form + secp256r1_UCG = + new byte[] { + (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, + (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, + (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, + (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, + (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, + (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, + (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, + (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, + (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, + (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, + (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 + }; + + secp256r1_N = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, + (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, + (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, + (byte) 0x25, (byte) 0x51 + }; + } + + private KMPoolManager() { + initStatics(); + cipherPool = new Object[(short) (CIPHER_ALGS.length * MAX_OPERATION_INSTANCES)]; + // Extra 4 algorithms are used to support TRUSTED_CONFIRMATION_REQUIRED feature. + signerPool = + new Object[(short) ((SIG_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + keyAgreementPool = new Object[(short) (KEY_AGREE_ALGS.length * MAX_OPERATION_INSTANCES)]; + + keysPool = + new Object[(short) ((KEY_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + operationPool = new Object[MAX_OPERATION_INSTANCES]; + hmacSignOperationPool = new Object[MAX_OPERATION_INSTANCES]; + /* Initialize pools */ + initializeOperationPool(); + initializeHmacSignOperationPool(); + initializeSignerPool(); + initializeCipherPool(); + initializeKeyAgreementPool(); + initializeKeysPool(); + // Initialize the Crypto and Key objects required for RKP flow. + initializeRKpObjects(); + } + + private void initializeRKpObjects() { + rkpOPeration = new KMOperationImpl(); + rkpAesGcm = Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + rkpHmac = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + rkpAesKey = createKeyObjectInstance(AES_256); + rkpHmacKey = createKeyObjectInstance(KMType.HMAC); + } + + private void initializeKeysPool() { + for (short index = 0; index < KEY_ALGS.length; index++) { + keysPool[index] = createKeyObjectInstance(KEY_ALGS[index]); + } + } + + private void initializeOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + operationPool[index] = new KMOperationImpl(); + } + } + + private void initializeHmacSignOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + hmacSignOperationPool[index] = new KMOperationImpl(); + } + } + + // Create a signature instance of each algorithm once. + private void initializeSignerPool() { + short index; + for (index = 0; index < SIG_ALGS.length; index++) { + signerPool[index] = getSignatureInstance(SIG_ALGS[index]); + } + + // Allocate extra 4 HMAC signer instances required for trusted confirmation + for (short len = (short) (index + 4); index < len; index++) { + signerPool[index] = getSignatureInstance(Signature.ALG_HMAC_SHA_256); + } + } + + // Create a cipher instance of each algorithm once. + private void initializeCipherPool() { + for (short index = 0; index < CIPHER_ALGS.length; index++) { + cipherPool[index] = getCipherInstance(CIPHER_ALGS[index]); + } + } + + private void initializeKeyAgreementPool() { + for (short index = 0; index < KEY_AGREE_ALGS.length; index++) { + keyAgreementPool[index] = getKeyAgreementInstance(KEY_AGREE_ALGS[index]); + } + } + + private Object[] getCryptoPoolInstance(short purpose) { + switch (purpose) { + case KMType.AGREE_KEY: + return keyAgreementPool; + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return cipherPool; + + case KMType.SIGN: + case KMType.VERIFY: + return signerPool; + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private Object createInstance(short purpose, short alg) { + switch (purpose) { + case KMType.AGREE_KEY: + return getKeyAgreementInstance((byte) alg); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return getCipherInstance((byte) alg); + + case KMType.SIGN: + case KMType.VERIFY: + return getSignatureInstance((byte) alg); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private KeyAgreement getKeyAgreementInstance(byte alg) { + return KeyAgreement.getInstance(alg, false); + } + + private Signature getSignatureInstance(byte alg) { + if (KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD == alg + || KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST == alg) { + return new KMRsa2048NoDigestSignature(alg); + } else if (KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST == alg) { + return new KMEcdsa256NoDigestSignature(alg); + } else { + return Signature.getInstance(alg, false); + } + } + + private KMKeyObject createKeyObjectInstance(byte alg) { + Object keyObject = null; + switch (alg) { + case AES_128: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + break; + case AES_256: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + break; + case KMType.DES: + keyObject = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + break; + case KMType.RSA: + keyObject = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + break; + case KMType.EC: + keyObject = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + initECKey((KeyPair) keyObject); + break; + case KMType.HMAC: + keyObject = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + KMKeyObject ptr = new KMKeyObject(); + ptr.algorithm = alg; + ptr.keyObjectInst = keyObject; + return ptr; + } + + private Cipher getCipherInstance(byte alg) { + if ((KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 == alg) + || (KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 == alg)) { + return new KMRsaOAEPEncoding(alg); + } else { + return Cipher.getInstance(alg, false); + } + } + + /** + * Returns the first available resource from operation pool. + * + * @return instance of the available resource or null if no resource is available. + */ + public KMOperation getResourceFromOperationPool(boolean isTrustedConfOpr) { + short index = 0; + KMOperationImpl impl; + Object[] oprPool; + if (isTrustedConfOpr) { + oprPool = hmacSignOperationPool; + } else { + oprPool = operationPool; + } + while (index < oprPool.length) { + impl = (KMOperationImpl) oprPool[index]; + // Mode is always set. so compare using mode value. + if (impl.getPurpose() == KMType.INVALID_VALUE) { + return impl; + } + index++; + } + return null; + } + + private byte getAlgorithm(short purpose, Object object) { + switch (purpose) { + case KMType.AGREE_KEY: + return ((KeyAgreement) object).getAlgorithm(); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return ((Cipher) object).getAlgorithm(); + + case KMType.SIGN: + case KMType.VERIFY: + return ((Signature) object).getAlgorithm(); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return 0; + } + + private boolean isResourceBusy(Object obj, byte resourceType) { + short index = 0; + while (index < MAX_OPERATION_INSTANCES) { + if (((KMOperationImpl) operationPool[index]).isResourceMatches(obj, resourceType) + || ((KMOperationImpl) hmacSignOperationPool[index]) + .isResourceMatches(obj, resourceType)) { + return true; + } + index++; + } + return false; + } + + private void setObject(short purpose, KMOperation operation, Object obj) { + switch (purpose) { + case KMType.AGREE_KEY: + ((KMOperationImpl) operation).setKeyAgreement((KeyAgreement) obj); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + ((KMOperationImpl) operation).setCipher((Cipher) obj); + break; + case KMType.SIGN: + case KMType.VERIFY: + ((KMOperationImpl) operation).setSignature((Signature) obj); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } + + private void reserveOperation( + KMOperation operation, + short purpose, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + Object obj, + KMKeyObject keyObject) { + ((KMOperationImpl) operation).setPurpose(purpose); + ((KMOperationImpl) operation).setAlgorithmType(strongboxAlgType); + ((KMOperationImpl) operation).setPaddingAlgorithm(padding); + ((KMOperationImpl) operation).setBlockMode(blockMode); + ((KMOperationImpl) operation).setMacLength(macLength); + ((KMOperationImpl) operation).setKeyObject(keyObject); + setObject(purpose, operation, obj); + } + + public KMOperation getRKpOperation( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength) { + if (((KMOperationImpl) rkpOPeration).getPurpose() != KMType.INVALID_VALUE) { + // Should not come here. + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Object cryptoObj = null; + KMKeyObject keyObject = null; + + switch (alg) { + case AEADCipher.ALG_AES_GCM: + cryptoObj = rkpAesGcm; + keyObject = rkpAesKey; + break; + case Signature.ALG_HMAC_SHA_256: + cryptoObj = rkpHmac; + keyObject = rkpHmacKey; + break; + default: + // Should not come here. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + reserveOperation( + rkpOPeration, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + cryptoObj, + keyObject); + return rkpOPeration; + } + + public KMOperation getOperationImpl( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + short secretLength, + boolean isTrustedConfOpr) { + KMOperation operation; + // Throw exception if no resource from operation pool is available. + if (null == (operation = getResourceFromOperationPool(isTrustedConfOpr))) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + // Get one of the pool instances (cipher / signer / keyAgreement) based on purpose. + Object[] pool = getCryptoPoolInstance(purpose); + short index = 0; + short usageCount = 0; + short maxOperations = MAX_OPERATION_INSTANCES; + if (Signature.ALG_HMAC_SHA_256 == alg) { + maxOperations = HMAC_MAX_OPERATION_INSTANCES; + } + + KMKeyObject keyObject = getKeyObjectFromPool(alg, secretLength, maxOperations); + while (index < pool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (pool[index] == null) { + // Create one of the instance (Cipher / Signer / KeyAgreement] based on purpose. + Object cipherObject = createInstance(purpose, alg); + JCSystem.beginTransaction(); + pool[index] = cipherObject; + JCSystem.commitTransaction(); + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + if (alg == getAlgorithm(purpose, pool[index])) { + // Check if the crypto instance is not busy and free to use. + if (!isResourceBusy(pool[index], RESOURCE_TYPE_CRYPTO)) { + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + usageCount++; + } + index++; + } + return operation; + } + + public KMKeyObject getKeyObjectFromPool(short alg, short secretLength, short maxOperations) { + KMKeyObject keyObject = null; + byte algo = mapAlgorithm(alg, secretLength); + short index = 0; + short usageCount = 0; + while (index < keysPool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (keysPool[index] == null) { + keyObject = createKeyObjectInstance(algo); + JCSystem.beginTransaction(); + keysPool[index] = keyObject; + JCSystem.commitTransaction(); + break; + } + keyObject = (KMKeyObject) keysPool[index]; + if (algo == keyObject.algorithm) { + // Check if the Object instance is not busy and free to use. + if (!isResourceBusy(keyObject, RESOURCE_TYPE_KEY)) { + break; + } + usageCount++; + } + index++; + } + return keyObject; + } + + private byte mapAlgorithm(short alg, short secretLength) { + byte algo = 0; + switch (alg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_AES_CTR: + case AEADCipher.ALG_AES_GCM: + if (secretLength == 16) { + algo = AES_128; + } else if (secretLength == 32) { + algo = AES_256; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + case Cipher.ALG_DES_CBC_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + algo = KMType.DES; + break; + case Cipher.ALG_RSA_PKCS1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + case Cipher.ALG_RSA_NOPAD: + case Signature.ALG_RSA_SHA_256_PKCS1: + case Signature.ALG_RSA_SHA_256_PKCS1_PSS: + case KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD: + case KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST: + algo = KMType.RSA; + break; + case Signature.ALG_ECDSA_SHA_256: + case KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST: + case KeyAgreement.ALG_EC_SVDP_DH_PLAIN: + algo = KMType.EC; + break; + case Signature.ALG_HMAC_SHA_256: + algo = KMType.HMAC; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return algo; + } + + public void initECKey(KeyPair ecKeyPair) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + ECPublicKey pubkey = (ECPublicKey) ecKeyPair.getPublic(); + pubkey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + pubkey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + pubkey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + pubkey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + pubkey.setK(secp256r1_H); + pubkey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + + privKey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + privKey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + privKey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + privKey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + privKey.setK(secp256r1_H); + privKey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + } + + public void powerReset() { + short index = 0; + while (index < operationPool.length) { + ((KMOperationImpl) operationPool[index]).abort(); + ((KMOperationImpl) hmacSignOperationPool[index]).abort(); + index++; + } + // release rkp operation + rkpOPeration.abort(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java new file mode 100644 index 0000000..8f94c82 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java @@ -0,0 +1,23 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMPreSharedKey is a marker interface and the SE Provider has to implement this interface. + * Internally Preshared key is stored as a Javacard HMac key object, which will provide additional + * security. The pre-shared key is maintained by the SEProvider. + */ +public interface KMPreSharedKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java new file mode 100644 index 0000000..fe6ad84 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java @@ -0,0 +1,3 @@ +package com.android.javacard.seprovider; + +public interface KMRkpMacKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java new file mode 100644 index 0000000..287a449 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -0,0 +1,139 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +public class KMRsa2048NoDigestSignature extends Signature { + + public static final byte ALG_RSA_SIGN_NOPAD = (byte) 0x65; + public static final byte ALG_RSA_PKCS1_NODIGEST = (byte) 0x66; + private byte algorithm; + private Cipher inst; + + public KMRsa2048NoDigestSignature(byte alg) { + algorithm = alg; + inst = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return algorithm; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] bytes, short i, short i1) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + padData(bytes, i, i1, KMAndroidSEProvider.getInstance().tmpArray, (short) 0); + return inst.doFinal( + KMAndroidSEProvider.getInstance().tmpArray, (short) 0, (short) 256, bytes1, i2); + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } + + private void padData(byte[] buf, short start, short len, byte[] outBuf, short outBufStart) { + if (!isValidData(buf, start, len)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(outBuf, (short) outBufStart, (short) 256, (byte) 0x00); + if (algorithm == ALG_RSA_SIGN_NOPAD) { // add zero to right + } else if (algorithm == ALG_RSA_PKCS1_NODIGEST) { // 0x00||0x01||PS||0x00 + outBuf[0] = 0x00; + outBuf[1] = 0x01; + Util.arrayFillNonAtomic(outBuf, (short) 2, (short) (256 - len - 3), (byte) 0xFF); + outBuf[(short) (256 - len - 1)] = 0x00; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + Util.arrayCopyNonAtomic(buf, start, outBuf, (short) (256 - len), len); + } + + private boolean isValidData(byte[] buf, short start, short len) { + if (algorithm == ALG_RSA_SIGN_NOPAD) { + if (len > 256) { + return false; + } + } else { // ALG_RSA_PKCS1_NODIGEST + if (len > 245) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + return false; + } + } + return true; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java new file mode 100644 index 0000000..74322bd --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java @@ -0,0 +1,288 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacardx.crypto.Cipher; + +public class KMRsaOAEPEncoding extends Cipher { + + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 = (byte) 0x1E; + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 = (byte) 0x1F; + + final short MGF1_BUF_SIZE = 256; + static byte[] mgf1Buf; + private Cipher cipher; + private byte hash; + private byte mgf1Hash; + private byte algorithm; + + public KMRsaOAEPEncoding(byte alg) { + setDigests(alg); + cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + algorithm = alg; + if (null == mgf1Buf) { + mgf1Buf = + JCSystem.makeTransientByteArray(MGF1_BUF_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT); + } + } + + private void setDigests(byte alg) { + switch (alg) { + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA; + break; + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA_256; + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + } + + private short getDigestLength() { + switch (hash) { + case MessageDigest.ALG_SHA: + return MessageDigest.LENGTH_SHA; + case MessageDigest.ALG_SHA_224: + return MessageDigest.LENGTH_SHA_224; + case MessageDigest.ALG_SHA_256: + return MessageDigest.LENGTH_SHA_256; + case MessageDigest.ALG_SHA_384: + return MessageDigest.LENGTH_SHA_384; + case MessageDigest.ALG_SHA_512: + return MessageDigest.LENGTH_SHA_512; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return 0; + } + + @Override + public void init(Key theKey, byte theMode) throws CryptoException { + cipher.init(theKey, theMode); + } + + @Override + public void init(Key theKey, byte theMode, byte[] bArray, short bOff, short bLen) + throws CryptoException { + cipher.init(theKey, theMode, bArray, bOff, bLen); + } + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short doFinal( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + short len = cipher.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + + // https://tools.ietf.org/html/rfc8017#section-7.1 + // https://www.inf.pucrs.br/~calazans/graduate/TPVLSI_I/RSA-oaep_spec.pdf + // RSA OAEP Encoding and Decoding Mechanism for a 2048 bit RSA Key. + // Msg -> RSA-OAEP-ENCODE -> RSAEncryption -> RSADecryption -> + // RSA-OAEP-DECODE -> Msg + // RSA-OAEP-ENCODE generates an output length of 255, but RSAEncryption + // requires and input of length 256 so we pad 0 to the left of the input + // message and make the length equal to 256 and pass to RSAEncryption. + // RSADecryption takes input length equal to 256 and generates an + // output of length 256. After decryption the first byte of the output + // should be 0(left padding we did in encryption). + // RSA-OAEP-DECODE takes input of length 255 so remove the left padding of 1 + // byte. + if (len != 256 || outBuff[0] != 0) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayCopyNonAtomic( + outBuff, (short) (outOffset + 1), outBuff, (short) 0, (short) (len - 1)); + return rsaOAEPDecode(outBuff, (short) 0, (short) (len - 1)); + } + + @Override + public short update( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + return cipher.update(inBuff, inOffset, inLength, outBuff, outOffset); + } + + private void maskGenerationFunction1( + byte[] input, + short inputOffset, + short inputLen, + short expectedOutLen, + byte[] outBuf, + short outOffset) { + short counter = 0; + MessageDigest.OneShot md = null; + try { + md = MessageDigest.OneShot.open(mgf1Hash); + short digestLen = md.getLength(); + + Util.arrayCopyNonAtomic(input, inputOffset, mgf1Buf, (short) 0, inputLen); + while (counter < (short) (expectedOutLen / digestLen)) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + counter++; + } + + if ((short) (counter * digestLen) < expectedOutLen) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + } + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(mgf1Buf, (short) 0, (short) MGF1_BUF_SIZE, (byte) 0); + } + } + + // Integer to Octet String conversion. + private void I2OS(short i, byte[] out, short offset) { + Util.arrayFillNonAtomic(out, (short) offset, (short) 4, (byte) 0); + out[(short) (offset + 3)] = (byte) (i >>> 0); + out[(short) (offset + 2)] = (byte) (i >>> 8); + } + + private short rsaOAEPDecode(byte[] encodedMsg, short encodedMsgOff, short encodedMsgLen) { + MessageDigest.OneShot md = null; + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + + try { + short hLen = getDigestLength(); + + if (encodedMsgLen < (short) (2 * hLen + 1)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // encodedMsg will be in the format of maskedSeed||maskedDB. + // maskedSeed length is hLen and maskedDB length is (encodedMsgLen - hLen) + // Now retrieve the seedMask by calling MGF(maskedDB, hLen). The length + // of the seedMask is hLen. + // seedMask = MGF(maskedDB, hLen) + maskGenerationFunction1( + encodedMsg, + (short) (encodedMsgOff + hLen), + (short) (encodedMsgLen - hLen), + hLen, + tmpArray, + (short) 0); + + // Get the seed by doing XOR of (maskedSeed ^ seedMask). + // seed = (maskedSeed ^ seedMask) + for (short i = 0; i < hLen; i++) { + // Store the seed in encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i)] ^= tmpArray[i]; + } + + // Now get the dbMask by calling MGF(seed , (emLen-hLen)). + // dbMask = MGF(seed , (emLen-hLen)). + maskGenerationFunction1( + encodedMsg, + (short) encodedMsgOff, + hLen, + (short) (encodedMsgLen - hLen), + tmpArray, + (short) 0); + + // Get the DB value. DB = (maskedDB ^ dbMask) + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + for (short i = 0; i < (short) (encodedMsgLen - hLen); i++) { + // Store the DB inside encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i + hLen)] ^= tmpArray[i]; + } + + // Verify Hash. + md = MessageDigest.OneShot.open(hash); + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + md.doFinal(tmpArray, (short) 0, (short) 0, tmpArray, (short) 0); + if (0 + != Util.arrayCompare( + encodedMsg, (short) (encodedMsgOff + hLen), tmpArray, (short) 0, hLen)) { + // Verification failed. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + // Find the Message block in DB. + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + // The message will be located at the end of the Data block (DB). + // The DB block is first constructed by keeping the message at the end and + // to the message 0x01 byte is prepended. The hash of the + // encoding parameters is calculated and then copied from the + // starting of the block and a variable length of 0's are + // appended to the end of the hash till the 0x01 byte. + short start = (short) (encodedMsgOff + encodedMsgLen); + for (short i = (short) (encodedMsgOff + 2 * hLen); + i < (short) (encodedMsgOff + encodedMsgLen); + i++) { + if ((encodedMsg[i] != 0)) { + start = i; + break; + } + } + if ((start >= (short) (encodedMsgOff + encodedMsgLen)) || (encodedMsg[start] != 0x01)) { + // Bad Padding. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + start++; // Message starting pos. + if (start < (short) (encodedMsgOff + encodedMsgLen)) { + // Copy the message + Util.arrayCopyNonAtomic( + encodedMsg, + start, + encodedMsg, + encodedMsgOff, + (short) (encodedMsgLen - (start - encodedMsgOff))); + } + return (short) (encodedMsgLen - (start - encodedMsgOff)); + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(tmpArray, (short) 0, KMAndroidSEProvider.TMP_ARRAY_SIZE, (byte) 0); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java new file mode 100644 index 0000000..1a564cc --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java @@ -0,0 +1,804 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** + * KMSEProvider is facade to use SE specific methods. The main intention of this interface is to + * abstract the cipher, signature and backup and restore related functions. The instance of this + * interface is created by the singleton KMSEProviderImpl class for each provider. At a time there + * can be only one provider in the applet package. + */ +public interface KMSEProvider { + + /** + * This function tells if boot signal event is supported or not. + * + * @return true if supported, false otherwise. + */ + boolean isBootSignalEventSupported(); + + /** + * This function tells if the device is booted or not. + * + * @return true if device booted, false otherwise. + */ + boolean isDeviceRebooted(); + + /** + * This function is supposed to be used to reset the device booted stated after set boot param is + * handled + * + * @param resetBootFlag is false if event has been handled + */ + void clearDeviceBooted(boolean resetBootFlag); + + /** + * Create a symmetric key instance. If the algorithm and/or keysize are not supported then it + * should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer in which key has to be returned + * @param startOff is the start offset. + * @return length of the data in the buf. This should match the keysize (in bytes). + */ + short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff); + + /** + * Create a asymmetric key pair. If the algorithms are not supported then it should throw a + * CryptoException. For RSA the public key exponent must always be 0x010001. The key size of RSA + * key pair must be 2048 bits and key size of EC key pair must be for p256 curve. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer to return the private key exponent in case of RSA or private + * key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyMaxLength is the maximum length of this private key buffer. + * @param pubModBuf is the buffer to return the modulus in case of RSA or public key in case of + * EC. + * @param pubModStart is the start of offset. + * @param pubModMaxLength is the maximum length of this public key buffer. + * @param lengths is the actual length of the key pair - lengths[0] should be private key and + * lengths[1] should be public key. + */ + void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyMaxLength, + byte[] pubModBuf, + short pubModStart, + short pubModMaxLength, + short[] lengths); + + /** + * Initializes the trusted confirmation operation. + * + * @param computedHmacKey Instance of the computed Hmac key. + * @return instance of KMOperation. + */ + KMOperation initTrustedConfirmationSymmetricOperation(KMComputedHmacKey computedHmacKey); + + /** + * Verify that the imported key is valid. If the algorithm and/or keysize are not supported then + * it should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer that contains the symmetric key. + * @param startOff is the start offset. + * @param length of the data in the buf. This should match the keysize (in bytes). + * @return true if the symmetric key is supported and valid. + */ + boolean importSymmetricKey(byte alg, short keysize, byte[] buf, short startOff, short length); + + /** + * Validate that the imported asymmetric key pair is valid. For RSA the public key exponent must + * always be 0x010001. The key size of RSA key pair must be 2048 bits and key size of EC key pair + * must be for p256 curve. If the algorithms are not supported then it should throw a + * CryptoException. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer that contains the private key exponent in case of RSA or + * private key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyLength is the length of this private key buffer. + * @param pubModBuf is the buffer that contains the modulus in case of RSA or public key in case + * of EC. + * @param pubModStart is the start of offset. + * @param pubModLength is the length of this public key buffer. + * @return true if the key pair is supported and valid. + */ + boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This is a oneshot operation that generates random number of desired length. + * + * @param num is the buffer in which random number is returned to the applet. + * @param offset is start of the buffer. + * @param length indicates the size of buffer and desired length of random number in bytes. + */ + void newRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that adds the entropy to the entropy pool. This operation + * corresponds to addRndEntropy command. This method may ignore the added entropy value if the SE + * provider does not support it. + * + * @param num is the buffer in which entropy value is given. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void addRngEntropy(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that generates and returns back a true random number. + * + * @param num is the buffer in which entropy value is returned. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void getTrueRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that performs encryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported or if tag length is not equal to 16 or + * nonce length is not equal to 12. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param data is the buffer that contains data to encrypt. + * @param dataStart is the start of the data buffer. + * @param dataLen is the length of the data buffer. + * @param encData is the buffer of the output encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return length of the encrypted data. + */ + short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] data, + short dataStart, + short dataLen, + byte[] encData, + short encDataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs decryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param encData is the buffer of the input encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param encDataLen is the length of the data buffer. + * @param data is the buffer that contains output decrypted data. + * @param dataStart is the start of the data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return true if the authentication is valid. + */ + boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encData, + short encDataStart, + short encDataLen, + byte[] data, + short dataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs key derivation function using cmac kdf (CKDF) as + * defined in android keymaster hal definition. + * + * @param hmacKey of pre-shared key. + * @param label is the label to be used for ckdf. + * @param labelStart is the start of label. + * @param labelLen is the length of the label. + * @param context is the context to be used for ckdf. + * @param contextStart is the start of the context + * @param contextLength is the length of the context + * @param key is the output buffer to return the derived key + * @param keyStart is the start of the output buffer. + * @return length of the derived key buffer in bytes. + */ + short cmacKDF( + KMPreSharedKey hmacKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] key, + short keyStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param hmacKey is the KMHmacKey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + Object hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. This is used to derive + * the key, which is used to encrypt the keyblob. + * + * @param masterkey of masterkey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacKDF( + KMMasterKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that verifies the signature using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the signature buffer. + * @param signatureStart is the start of the signature buffer. + * @param signatureLen is the length of the signature buffer in bytes. + * @return true if the signature matches. + */ + boolean hmacVerify( + KMComputedHmacKey hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart, + short signatureLen); + + /** + * This is a oneshot operation that decrypts the data using RSA algorithm with oaep256 padding. + * The public exponent is always 0x010001. It throws CryptoException if OAEP encoding validation + * fails. + * + * @param privExp is the private exponent (2048 bit) buffer. + * @param privExpStart is the start of the private exponent buffer. + * @param privExpLength is the length of the private exponent buffer in bytes. + * @param modBuffer is the modulus (2048 bit) buffer. + * @param modOff is the start of the modulus buffer. + * @param modLength is the length of the modulus buffer in bytes. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the decrypted data. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short rsaDecipherOAEP256( + byte[] privExp, + short privExpStart, + short privExpLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * This is a oneshot operation that signs the data using EC private key. + * + * @param ecPrivKey of KMAttestationKey. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the inpur data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short ecSign256( + KMAttestationKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 + * + * @param ikm is the buffer containing input key material. + * @param ikmOff is the start of the input key. + * @param ikmLen is the length of the input key. + * @param salt is the buffer containing the salt. + * @param saltOff is the start of the salt buffer. + * @param saltLen is the length of the salt buffer. + * @param info is the buffer containing the application specific information + * @param infoOff is the start of the info buffer. + * @param infoLen is the length of the info buffer. + * @param out is the output buffer. + * @param outOff is the start of the output buffer. + * @param outLen is the length of the expected out buffer. + * @return Length of the out buffer which is outLen. + */ + short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen); + + /** + * This function performs ECDH key agreement and generates a secret. + * + * @param privKey is the buffer containing the private key from first party. + * @param privKeyOff is the offset of the private key buffer. + * @param privKeyLen is the length of the private key buffer. + * @param publicKey is the buffer containing the public key from second party. + * @param publicKeyOff is the offset of the public key buffer. + * @param publicKeyLen is the length of the public key buffer. + * @param secret is the output buffer. + * @param secretOff is the offset of the output buffer. + * @return The length of the secret. + */ + short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff); + + /** + * This is a oneshort operation that verifies the data using EC public key + * + * @param pubKey is the public key buffer. + * @param pubKeyOffset is the start of the public key buffer. + * @param pubKeyLen is the length of the public key. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param signatureDataBuf is the buffer the signature input data. + * @param signatureDataStart is the start of the signature input data. + * @param signatureDataLen is the length of the signature input data. + * @return true if verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen); + + /** + * This is a oneshot operation that signs the data using device unique key. + * + * @param ecPrivKey instance of KMECDeviceUniqueKey to sign the input data. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short ecSign256( + KMDeviceUniqueKeyPair ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param key is a key object. + * @param interfaceType defines the type of key in the key object. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @param oneShot if true, creates oneshot operation. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot); + + /** + * This function creates an Operation instance only for RKP module. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using RSA + * and EC algorithms when keymaster hal's beginOperation function is executed. For RSA the public + * exponent is always 0x0100101. For EC the curve is always p256. The KMOperation instance can be + * reclaimed by the seProvider when KMOperation is finished or aborted. It throws CryptoException + * if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for RSA. It will be * KMType.SIGN and + * KMType.VERIFY for RSA and EC algorithms. + * @param alg is KMType.RSA or KMType.EC algorithms. + * @param padding is KMType.PADDING_NONE or KMType.RSA_OAEP, KMType.RSA_PKCS1_1_5_ENCRYPT, + * KMType.RSA_PKCS1_1_5_SIGN or KMType.RSA_PSS. + * @param digest is KMType.DIGEST_NONE or KMType.SHA2_256. + * @param mgfDigest is the MGF digest. + * @param privKeyBuf is the private key in case of EC or private key exponent is case of RSA. + * @param privKeyStart is the start of the private key. + * @param privKeyLength is the length of the private key. + * @param pubModBuf is the modulus (in case of RSA) or public key (in case of EC). + * @param pubModStart is the start of the modulus. + * @param pubModLength is the length of the modulus. + * @return KMOperation instance that can be executed. + */ + KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This function tells if applet is upgrading or not. + * + * @return true if upgrading, otherwise false. + */ + boolean isUpgrading(); + + /** + * This function generates an AES Key of keySizeBits, which is used as an master key. This + * generated key is maintained by the SEProvider. This function should be called only once at the + * time of installation. + * + * @param instance of the masterkey. + * @param keySizeBits key size in bits. + * @return An instance of KMMasterKey. + */ + KMMasterKey createMasterKey(KMMasterKey masterKey, short keySizeBits); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMComputedHmacKey. + */ + KMComputedHmacKey createComputedHmacKey( + KMComputedHmacKey computedHmacKey, byte[] keyData, short offset, short length); + + /** Returns true if factory provisioned attestation key is supported. */ + boolean isAttestationKeyProvisioned(); + + /** + * Returns algorithm type of the attestation key. It can be KMType.EC or KMType.RSA if the + * attestation key is provisioned in the factory. + */ + short getAttestationKeyAlgorithm(); + + /** + * Creates an ECKey instance and sets the public and private keys to it. + * + * @param testMode to indicate if current execution is for test or production. + * @param pubKey buffer containing the public key. + * @param pubKeyOff public key buffer start offset. + * @param pubKeyLen public key buffer length. + * @param privKey buffer containing the private key. + * @param privKeyOff private key buffer start offset. + * @param privKeyLen private key buffer length. + * @return instance of KMDeviceUniqueKey. + */ + KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( + KMDeviceUniqueKeyPair key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen); + + /** + * This is a one-shot operation the does digest of the input mesage. + * + * @param inBuff input buffer to be digested. + * @param inOffset start offset of the input buffer. + * @param inLength length of the input buffer. + * @param outBuff is the output buffer that contains the digested data. + * @param outOffset start offset of the digested output buffer. + * @return length of the digested data. + */ + short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset); + + /** + * This function generates a HMAC key from the provided key buffers. + * + * @param presharedKey instance of the presharedkey. + * @param key buffer containing the key data. + * @param offset start offset of the buffer. + * @param length is the length of the key. + * @return instance of KMPresharedKey. + */ + KMPreSharedKey createPreSharedKey( + KMPreSharedKey presharedKey, byte[] key, short offset, short length); + + /** + * This function saves the key objects while upgrade. + * + * @param element instance of the Element class where the objects to be stored. + * @param interfaceType the type interface of the parent object. + * @param object instance of the object to be saved. + */ + void onSave(Element element, byte interfaceType, Object object); + + /** + * This function restores the the object from element instance. + * + * @param element instance of the Element class. + * @return restored object. + */ + Object onRestore(Element element); + + /** + * This function returns the count of the primitive bytes required to be stored by the + * implementation of the interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the primitive bytes. + */ + short getBackupPrimitiveByteCount(byte interfaceType); + + /** + * This function returns the object count required to be stored by the implementation of the + * interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the objects. + */ + short getBackupObjectCount(byte interfaceType); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMRkpMacKey. + */ + KMRkpMacKey createRkpMacKey( + KMRkpMacKey createComputedHmacKey, byte[] keyData, short offset, short length); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java new file mode 100644 index 0000000..648d323 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java @@ -0,0 +1,82 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; + + +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java new file mode 100644 index 0000000..69536ab --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java @@ -0,0 +1,29 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +public interface KMUpgradable { + + void onSave(Element ele); + + void onRestore(Element ele, short oldVersion, short currentVersion); + + short getBackupPrimitiveByteCount(); + + short getBackupObjectCount(); +} diff --git a/ready_se/google/keymint/KM200/Applet/README.md b/ready_se/google/keymint/KM200/Applet/README.md new file mode 100644 index 0000000..ace6950 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/README.md @@ -0,0 +1,15 @@ +# JavaCardKeymaster Applet + +This directory contains the implementation of the Keymint 1.0 +interface, in the form of a JavaCard 3.0.5 applet which runs in a secure +element. It must be deployed in conjuction with the associated HAL, +which mediates between Android Keystore and this applet. + +# Supported Features! + + - Keymint 1.0 supported functions for required VTS compliance. + - SharedSecret 1.0 supported functions for required VTS compliance. + +# Not supported features + - Factory provisioned attestation key will not be supported in this applet. + - Limited usage keys will not be supported in this applet. diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java new file mode 100644 index 0000000..aa54d54 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -0,0 +1,165 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMArray represents an array of KMTypes. Array is the sequence of elements of one or more sub + * types of KMType. It also acts as a vector of one subtype of KMTypes on the lines of class KMArray + * , where subType is subclass of KMType. Vector is the sequence of elements of one sub + * type e.g. KMType.BYTE_BLOB_TYPE. The KMArray instance maps to the CBOR type array. KMArray is a + * KMType and it further extends the value field in TLV_HEADER as ARRAY_HEADER struct{short subType; + * short length;} followed by sequence of short pointers to KMType instances. The subType can be 0 + * if this is an array or subType is short KMType value e.g. KMType.BYTE_BLOB_TYPE if this is a + * vector of that sub type. + */ +public class KMArray extends KMType { + + public static final short ANY_ARRAY_LENGTH = 0x1000; + private static final byte ARRAY_HEADER_SIZE = 4; + private static KMArray prototype; + + private KMArray() {} + + private static KMArray proto(short ptr) { + if (prototype == null) { + prototype = new KMArray(); + } + KMType.instanceTable[KM_ARRAY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short exp(short type) { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(ARRAY_TYPE, (short) (ARRAY_HEADER_SIZE + (length * 2))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMArray cast(short ptr) { + if (heap[ptr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short objPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (getStartOff() + (short) (index * 2)), objPtr); + } + + public short get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort(heap, (short) (getStartOff() + (short) (index * 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2)), + indexPtr1); + } + + public short containedType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); + } + + public short length() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short setLength(short len) { + return Util.setShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), len); + } + + public byte[] getBuffer() { + return heap; + } + + public void deleteLastEntry() { + short len = length(); + Util.setShort( + heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len - 1)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java new file mode 100644 index 0000000..65ca94f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -0,0 +1,434 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +public class KMAsn1Parser { + + public static final byte ASN1_OCTET_STRING = 0x04; + public static final byte ASN1_SEQUENCE = 0x30; + public static final byte ASN1_SET = 0x31; + public static final byte ASN1_INTEGER = 0x02; + public static final byte OBJECT_IDENTIFIER = 0x06; + public static final byte ASN1_A0_TAG = (byte) 0xA0; + public static final byte ASN1_A1_TAG = (byte) 0xA1; + public static final byte ASN1_BIT_STRING = 0x03; + + public static final byte ASN1_UTF8_STRING = 0x0C; + public static final byte ASN1_TELETEX_STRING = 0x14; + public static final byte ASN1_PRINTABLE_STRING = 0x13; + public static final byte ASN1_UNIVERSAL_STRING = 0x1C; + public static final byte ASN1_BMP_STRING = 0x1E; + public static final byte IA5_STRING = 0x16; + public static final byte[] EC_CURVE = { + 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 + }; + public static final byte[] RSA_ALGORITHM = { + 0x06,0x09,0x2A,(byte)0x86,0x48,(byte)0x86, + (byte)0xF7,0x0D,0x01,0x01,0x01,0x05,0x00 + }; + public static final byte[] EC_ALGORITHM = { + 0x06,0x07,0x2a,(byte)0x86,0x48,(byte)0xce, + 0x3d,0x02,0x01,0x06,0x08,0x2a,(byte)0x86,0x48, + (byte)0xce,0x3d,0x03,0x01,0x07 + }; + public static final short MAX_EMAIL_ADD_LEN = 255; + private static final byte DATA_START_OFFSET = 0; + private static final byte DATA_LENGTH_OFFSET = 1; + private static final byte DATA_CURSOR_OFFSET = 2; + // This array contains the last byte of OID for each oid type. + // The first 4 bytes are common as shown above in COMMON_OID + private static final byte[] attributeOIds = { + 0x03, /* commonName COMMON_OID.3 */ 0x04, /* surName COMMON_OID.4*/ + 0x05, /* serialNumber COMMON_OID.5 */ 0x06, /* countryName COMMON_OID.6 */ + 0x07, /* locality COMMON_OID.7 */ 0x08, /* stateOrProviince COMMON_OID.8 */ + 0x0A, /* organizationName COMMON_OID.10 */ 0x0B, /* organizationalUnitName COMMON_OID.11 */ + 0x0C, /* title COMMON_OID.10 */ 0x29, /* name COMMON_OID.41 */ + 0x2A, /* givenName COMMON_OID.42 */ 0x2B, /* initials COMMON_OID.43 */ + 0x2C, /* generationQualifier COMMON_OID.44 */ 0x2E, /* dnQualifer COMMON_OID.46 */ + 0x41, /* pseudonym COMMON_OID.65 */ + }; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 124 + // TODO Specification does not mention about the DN_QUALIFIER_OID max length. + // So the max limit is set at 64. + // For name the RFC 5280 supports up to 32768, as Javacard doesn't support + // that much length, the max limit for name is set to 128. + private static final byte[] attributeValueMaxLen = { + 0x40, /* 1-64 commonName */ + 0x28, /* 1-40 surname */ + 0x40, /* 1-64 serial */ + 0x02, /* 1-2 country */ + (byte) 0x80, /* 1-128 locality */ + (byte) 0x80, /* 1-128 state */ + 0x40, /* 1-64 organization */ + 0x40, /* 1-64 organization unit*/ + 0x40, /* 1-64 title */ + 0x29, /* 1-128 name */ + 0x10, /* 1-16 givenName */ + 0x05, /* 1-5 initials */ + 0x03, /* 1-3 gen qualifier */ + 0x40, /* 1-64 dn-qualifier */ + (byte) 0x80 /* 1-128 pseudonym */ + }; + private static KMAsn1Parser inst; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21 + // 2.5.4 + public byte[] COMMON_OID = new byte[] {0x06, 0x03, 0x55, 0x04}; + public byte[] EMAIL_ADDRESS_OID = + new byte[] { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x09, 0x01 + }; + private byte[] data; + private short[] dataInfo; + + private KMAsn1Parser() { + dataInfo = JCSystem.makeTransientShortArray((short) 3, JCSystem.CLEAR_ON_RESET); + dataInfo[DATA_START_OFFSET] = 0; + dataInfo[DATA_LENGTH_OFFSET] = 0; + dataInfo[DATA_CURSOR_OFFSET] = 0; + } + + public static KMAsn1Parser instance() { + if (inst == null) { + inst = new KMAsn1Parser(); + } + return inst; + } + + public short decodeRsa(short blob) { + init(blob); + decodeCommon((short) 0, RSA_ALGORITHM); + return decodeRsaPrivateKey((short) 0); + } + + public short decodeEc(short blob) { + init(blob); + decodeCommon((short) 0, EC_ALGORITHM); + return decodeEcPrivateKey((short) 1); + } + + /* + Name ::= CHOICE { -- only one possibility for now -- + rdnSequence RDNSequence } + RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + RelativeDistinguishedName ::= + SET SIZE (1..MAX) OF AttributeTypeAndValue + AttributeTypeAndValue ::= SEQUENCE { + type AttributeType, + value AttributeValue } + AttributeType ::= OBJECT IDENTIFIER + AttributeValue ::= ANY -- DEFINED BY AttributeType + */ + public void validateDerSubject(short blob) { + init(blob); + header(ASN1_SEQUENCE); + while (dataInfo[DATA_CURSOR_OFFSET] + < ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + header(ASN1_SET); + header(ASN1_SEQUENCE); + // Parse and validate OBJECT-IDENTIFIER and Value fields + // Cursor is incremented in validateAttributeTypeAndValue. + validateAttributeTypeAndValue(); + } + } + + public short decodeEcSubjectPublicKeyInfo(short blob) { + init(blob); + header(ASN1_SEQUENCE); + short len = header(ASN1_SEQUENCE); + short ecPublicInfo = KMByteBlob.instance(len); + getBytes(ecPublicInfo); + if (Util.arrayCompare( + KMByteBlob.cast(ecPublicInfo).getBuffer(), + KMByteBlob.cast(ecPublicInfo).getStartOff(), + EC_ALGORITHM, + (short) 0, + KMByteBlob.cast(ecPublicInfo).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + return pubKey; + } + + // Seq[Int,Int,Int,Int,] + public short decodeRsaPrivateKey(short version) { + short resp = KMArray.instance((short) 3); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_INTEGER); + short modulus = KMByteBlob.instance(len); + getBytes(modulus); + updateRsaKeyBuffer(modulus); + len = header(ASN1_INTEGER); + short pubKey = KMByteBlob.instance(len); + getBytes(pubKey); + len = header(ASN1_INTEGER); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + updateRsaKeyBuffer(privKey); + KMArray.cast(resp).add((short) 0, modulus); + KMArray.cast(resp).add((short) 1, pubKey); + KMArray.cast(resp).add((short) 2, privKey); + return resp; + } + + private void updateRsaKeyBuffer(short blob) { + byte[] buffer = KMByteBlob.cast(blob).getBuffer(); + short startOff = KMByteBlob.cast(blob).getStartOff(); + short len = KMByteBlob.cast(blob).length(); + if (0 == buffer[startOff] && len > 256) { + KMByteBlob.cast(blob).setStartOff(++startOff); + KMByteBlob.cast(blob).setLength(--len); + } + } + + private short readEcdsa256SigIntegerHeader() { + short len = header(ASN1_INTEGER); + if (len == 33) { + if (0 != getByte()) { + KMException.throwIt(KMError.INVALID_DATA); + } + len--; + } else if (len > 33) { + KMException.throwIt(KMError.INVALID_DATA); + } + return len; + } + + // Seq [Int, Int] + public short decodeEcdsa256Signature(short blob, byte[] scratchPad, short scratchPadOff) { + init(blob); + short len = header(ASN1_SEQUENCE); + len = readEcdsa256SigIntegerHeader(); + // concatenate r and s in the buffer (r||s) + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 64, (byte) 0); + // read r + getBytes(scratchPad, (short) (scratchPadOff + 32 - len), len); + len = readEcdsa256SigIntegerHeader(); + // read s + getBytes(scratchPad, (short) (scratchPadOff + 64 - len), len); + return (short) 64; + } + + // Seq [Int, Blob] + public void decodeCommon(short version, byte[] alg) { + short len = header(ASN1_SEQUENCE); + len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_SEQUENCE); + short blob = KMByteBlob.instance(len); + getBytes(blob); + if (Util.arrayCompare( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + alg, + (short) 0, + KMByteBlob.cast(blob).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + // Seq[Int,blob,blob] + public short decodeEcPrivateKey(short version) { + short resp = KMArray.instance((short) 2); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_OCTET_STRING); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + validateTag0IfPresent(); + header(ASN1_A1_TAG); + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + KMArray.cast(resp).add((short) 0, pubKey); + KMArray.cast(resp).add((short) 1, privKey); + return resp; + } + + private void validateTag0IfPresent() { + if (data[dataInfo[DATA_CURSOR_OFFSET]] != ASN1_A0_TAG) { + return; + } + ; + short len = header(ASN1_A0_TAG); + if (len != EC_CURVE.length) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (Util.arrayCompare(data, dataInfo[DATA_CURSOR_OFFSET], EC_CURVE, (short) 0, len) != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(len); + } + + private void validateAttributeTypeAndValue() { + // First byte should be OBJECT_IDENTIFIER, otherwise it is not well-formed DER Subject. + if (data[dataInfo[DATA_CURSOR_OFFSET]] != OBJECT_IDENTIFIER) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Check if the OID matches the email address + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + EMAIL_ADDRESS_OID, + (short) 0, + (short) EMAIL_ADDRESS_OID.length) + == 0)) { + incrementCursor((short) EMAIL_ADDRESS_OID.length); + // Validate the length of the attribute value. + if (getByte() != IA5_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short emailLength = getLength(); + if (emailLength <= 0 && emailLength > MAX_EMAIL_ADD_LEN) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(emailLength); + return; + } + // Check other OIDs. + for (short i = 0; i < (short) attributeOIds.length; i++) { + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + COMMON_OID, + (short) 0, + (short) COMMON_OID.length) + == 0) + && (attributeOIds[i] + == data[(short) (dataInfo[DATA_CURSOR_OFFSET] + COMMON_OID.length)])) { + incrementCursor((short) (COMMON_OID.length + 1)); + // Validate the length of the attribute value. + short tag = getByte(); + if (tag != ASN1_UTF8_STRING + && tag != ASN1_TELETEX_STRING + && tag != ASN1_PRINTABLE_STRING + && tag != ASN1_UNIVERSAL_STRING + && tag != ASN1_BMP_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short attrValueLength = getLength(); + if (attrValueLength <= 0 && attrValueLength > attributeValueMaxLen[i]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(attrValueLength); + return; + } + } + // If no match is found above then move the cursor to next element. + getByte(); // Move Cursor by one byte (OID) + incrementCursor(getLength()); // Move cursor to AtrributeTag + getByte(); // Move cursor to AttributeValue + incrementCursor(getLength()); // Move cursor to next SET element + } + + private short header(short tag) { + short t = getByte(); + if (t != tag) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return getLength(); + } + + private byte getByte() { + byte d = data[dataInfo[DATA_CURSOR_OFFSET]]; + incrementCursor((short) 1); + return d; + } + + private short getShort() { + short d = Util.getShort(data, dataInfo[DATA_CURSOR_OFFSET]); + incrementCursor((short) 2); + return d; + } + + private void getBytes(short blob) { + short len = KMByteBlob.cast(blob).length(); + Util.arrayCopyNonAtomic( + data, + dataInfo[DATA_CURSOR_OFFSET], + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + len); + incrementCursor(len); + } + + private void getBytes(byte[] buffer, short offset, short len) { + Util.arrayCopyNonAtomic(data, dataInfo[DATA_CURSOR_OFFSET], buffer, offset, len); + incrementCursor(len); + } + + private short getLength() { + byte len = getByte(); + if (len >= 0) { + return len; + } + len = (byte) (len & 0x7F); + if (len == 1) { + return (short) (getByte() & 0xFF); + } else if (len == 2) { + return getShort(); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMType.INVALID_VALUE; // should not come here + } + + public void init(short blob) { + data = KMByteBlob.cast(blob).getBuffer(); + dataInfo[DATA_START_OFFSET] = KMByteBlob.cast(blob).getStartOff(); + dataInfo[DATA_LENGTH_OFFSET] = KMByteBlob.cast(blob).length(); + dataInfo[DATA_CURSOR_OFFSET] = dataInfo[DATA_START_OFFSET]; + } + + public void incrementCursor(short n) { + dataInfo[DATA_CURSOR_OFFSET] += n; + if (dataInfo[DATA_CURSOR_OFFSET] + > ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java new file mode 100644 index 0000000..5eb7eae --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java @@ -0,0 +1,110 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBignumTag represents BIGNUM Tag Type from android keymaster hal specifications. The tag value + * of this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BIGNUM_TAG; short tagKey; short blobPtr}} + */ +public class KMBignumTag extends KMTag { + + private static KMBignumTag prototype; + + private KMBignumTag() {} + + private static KMBignumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBignumTag(); + } + KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMBignumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BIGNUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case CERTIFICATE_SERIAL_NUM: + if (valueLen > MAX_CERTIFICATE_SERIAL_SIZE) { + return false; + } + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BIGNUM_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java new file mode 100644 index 0000000..27730a5 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -0,0 +1,115 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBoolTag represents BOOL TAG type from the android keymaster hal specifications. If it is + * present in the key parameter list then its value is always true. A KMTag always requires a value + * because it is a key value pair. The bool tag always has 0x01 as its value. struct{byte TAG_TYPE; + * short length; struct{short BOOL_TAG; short tagKey; byte value 1}} + */ +public class KMBoolTag extends KMTag { + + // The allowed tag keys of type bool tag. + private static final short[] tags = { + CALLER_NONCE, + INCLUDE_UNIQUE_ID, + BOOTLOADER_ONLY, + ROLLBACK_RESISTANCE, + NO_AUTH_REQUIRED, + ALLOW_WHILE_ON_BODY, + TRUSTED_USER_PRESENCE_REQUIRED, + TRUSTED_CONFIRMATION_REQUIRED, + UNLOCKED_DEVICE_REQUIRED, + RESET_SINCE_ID_ROTATION, + EARLY_BOOT_ONLY, + DEVICE_UNIQUE_ATTESTATION + }; + private static KMBoolTag prototype; + + private KMBoolTag() {} + + private static KMBoolTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBoolTag(); + } + KMType.instanceTable[KM_BOOL_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateKey(key)) { + KMException.throwIt(KMError.INVALID_TAG); + } + short ptr = KMType.instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + // Value is always 1. + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = 0x01; + return ptr; + } + + public static KMBoolTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BOOL_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // validate the tag key. + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + public static short[] getTags() { + return tags; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BOOL_TAG; + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java new file mode 100644 index 0000000..98d81fc --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -0,0 +1,142 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteBlob represents contiguous block of bytes. It corresponds to CBOR type of Byte String. It + * extends KMType by specifying value field as zero or more sequence of bytes. struct{byte + * BYTE_BLOB_TYPE; short length; sequence of bytes} + */ +public class KMByteBlob extends KMType { + + private static byte OFFSET_SIZE = 2; + private static KMByteBlob prototype; + + protected KMByteBlob() {} + + private static KMByteBlob proto(short ptr) { + if (prototype == null) { + prototype = new KMByteBlob(); + } + KMType.instanceTable[KM_BYTE_BLOB_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(BYTE_BLOB_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(BYTE_BLOB_TYPE, (short) (length + 2)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMByteBlob + public static KMByteBlob cast(short ptr) { + if (heap[ptr] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // Add the byte + public void add(short index, byte val) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + heap[(short) (getStartOff() + index)] = val; + } + + // Get the byte + public byte get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return heap[(short) (getStartOff() + index)]; + } + + // Get the start of blob + public short getStartOff() { + return Util.getShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE)); + } + + public void setStartOff(short offset) { + Util.setShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE), offset); + } + + // Get the length of the blob + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + public void getValue(byte[] destBuf, short destStart, short destLength) { + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + } + + public short getValues(byte[] destBuf, short destStart) { + short destLength = length(); + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + return destLength; + } + + public void setValue(byte[] srcBuf, short srcStart, short srcLength) { + if (length() < srcLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.arrayCopyNonAtomic(srcBuf, srcStart, heap, getStartOff(), srcLength); + setLength(srcLength); + } + + public boolean isValid() { + return (length() != 0); + } + + protected short getBaseOffset() { + return instanceTable[KM_BYTE_BLOB_OFFSET]; + } + + public void setLength(short len) { + Util.setShort(heap, (short) (getBaseOffset() + 1), len); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java new file mode 100644 index 0000000..ac49c0e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -0,0 +1,145 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteTag represents BYTES Tag Type from android keymaster hal specifications. The tag value of + * this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BYTES_TAG; short tagKey; short blobPtr}} + */ +public class KMByteTag extends KMTag { + + private static KMByteTag prototype; + + private KMByteTag() {} + + private static KMByteTag proto(short ptr) { + if (prototype == null) { + prototype = new KMByteTag(); + } + KMType.instanceTable[KM_BYTE_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMByteTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BYTES_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case ATTESTATION_APPLICATION_ID: + if (valueLen > MAX_ATTESTATION_APP_ID_SIZE) { + return false; + } + break; + case CERTIFICATE_SUBJECT_NAME: + { + if (valueLen > KMConfigurations.MAX_SUBJECT_DER_LEN) { + return false; + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + asn1Decoder.validateDerSubject(byteBlob); + } + break; + case APPLICATION_ID: + case APPLICATION_DATA: + if (valueLen > MAX_APP_ID_APP_DATA_SIZE) { + return false; + } + break; + case ATTESTATION_CHALLENGE: + if (valueLen > MAX_ATTESTATION_CHALLENGE_SIZE) { + return false; + } + break; + case ATTESTATION_ID_BRAND: + case ATTESTATION_ID_DEVICE: + case ATTESTATION_ID_PRODUCT: + case ATTESTATION_ID_SERIAL: + case ATTESTATION_ID_IMEI: + case ATTESTATION_ID_MEID: + case ATTESTATION_ID_MANUFACTURER: + case ATTESTATION_ID_MODEL: + if (valueLen > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + return false; + } + break; + case ROOT_OF_TRUST: + case NONCE: + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BYTES_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java new file mode 100644 index 0000000..1eb8816 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -0,0 +1,608 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; + +/** + * This class constructs the Cose messages like CoseKey, CoseMac0, MacStructure, CoseSign1, + * SignStructure, CoseEncrypt, EncryptStructure and ReceipientStructures. + */ +public class KMCose { + + // COSE SIGN1 + public static final byte COSE_SIGN1_ENTRY_COUNT = 4; + public static final byte COSE_SIGN1_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_SIGN1_PAYLOAD_OFFSET = 2; + public static final byte COSE_SIGN1_SIGNATURE_OFFSET = 3; + // COSE MAC0 + public static final byte COSE_MAC0_ENTRY_COUNT = 4; + public static final byte COSE_MAC0_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_MAC0_PAYLOAD_OFFSET = 2; + public static final byte COSE_MAC0_TAG_OFFSET = 3; + // COSE ENCRYPT + public static final byte COSE_ENCRYPT_ENTRY_COUNT = 4; + public static final byte COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT = 3; + public static final byte COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT = 3; + + // COSE Labels + public static final byte COSE_LABEL_ALGORITHM = 1; + public static final byte COSE_LABEL_KEYID = 4; + public static final byte COSE_LABEL_IV = 5; + public static final byte COSE_LABEL_COSE_KEY = (byte) 0xFF; // -1 + + // COSE Algorithms + public static final byte COSE_ALG_AES_GCM_256 = 3; // AES-GCM mode w/ 256-bit key, 128-bit tag. + public static final byte COSE_ALG_HMAC_256 = 5; // HMAC w/ SHA-256 + public static final byte COSE_ALG_ES256 = (byte) 0xF9; // ECDSA w/ SHA-256; -7 + public static final byte COSE_ALG_ECDH_ES_HKDF_256 = (byte) 0xE7; // ECDH-EC+HKDF-256; -25 + + // COSE P256 EC Curve + public static final byte COSE_ECCURVE_256 = 1; + + // COSE key types + public static final byte COSE_KEY_TYPE_EC2 = 2; + public static final byte COSE_KEY_TYPE_SYMMETRIC_KEY = 4; + + // COSE Key Operations + public static final byte COSE_KEY_OP_SIGN = 1; + public static final byte COSE_KEY_OP_VERIFY = 2; + public static final byte COSE_KEY_OP_ENCRYPT = 3; + public static final byte COSE_KEY_OP_DECRYPT = 4; + + // AES GCM + public static final short AES_GCM_KEY_SIZE_BITS = 256; + // Cose key parameters. + public static final short COSE_KEY_KEY_TYPE = 1; + public static final short COSE_KEY_KEY_ID = 2; + public static final short COSE_KEY_ALGORITHM = 3; + public static final short COSE_KEY_KEY_OPS = 4; + public static final short COSE_KEY_CURVE = -1; + public static final short COSE_KEY_PUBKEY_X = -2; + public static final short COSE_KEY_PUBKEY_Y = -3; + public static final short COSE_KEY_PRIV_KEY = -4; + public static final byte[] COSE_TEST_KEY = { + (byte) 0xFF, (byte) 0xFE, (byte) 0xEE, (byte) 0x90 + }; // -70000 + public static final byte COSE_KEY_MAX_SIZE = 4; + + // kdfcontext strings + public static final byte[] client = {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + public static final byte[] server = {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + // Context strings + public static final byte[] MAC_CONTEXT = {0x4d, 0x41, 0x43, 0x30}; // MAC0 + public static final byte[] SIGNATURE1_CONTEXT = { + 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31 + }; // Signature1 + public static final byte[] ENCRYPT_CONTEXT = { + 0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74 + }; // Encrypt + // Certificate payload supported keys + public static final byte ISSUER = (byte) 0x01; + public static final byte SUBJECT = (byte) 0x02; + public static final byte[] SUBJECT_PUBLIC_KEY = { + (byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8 + }; + public static final byte[] KEY_USAGE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + public static final byte[] TEST_ISSUER_NAME = { + (byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72 + }; // "Issuer" + public static final byte[] TEST_SUBJECT_NAME = { + 0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74 + }; // "Subject" + public static final byte[] KEY_USAGE_SIGN = {0x20}; // Key usage sign + + public static final short[] COSE_KEY_LABELS = { + KMCose.COSE_KEY_KEY_TYPE, + KMCose.COSE_KEY_KEY_ID, + KMCose.COSE_KEY_ALGORITHM, + KMCose.COSE_KEY_KEY_OPS, + KMCose.COSE_KEY_CURVE, + KMCose.COSE_KEY_PUBKEY_X, + KMCose.COSE_KEY_PUBKEY_Y, + KMCose.COSE_KEY_PRIV_KEY + }; + public static final short[] COSE_HEADER_LABELS = { + KMCose.COSE_LABEL_ALGORITHM, + KMCose.COSE_LABEL_KEYID, + KMCose.COSE_LABEL_IV, + KMCose.COSE_LABEL_COSE_KEY + }; + + /** + * Constructs the Cose MAC structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the external Aad. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @return KMArray instance of MAC structure. + */ + public static short constructCoseMacStructure( + short protectedHeader, short extAad, short payload) { + // Create MAC Structure and compute HMAC as per https://tools.ietf.org/html/rfc8152#section-6.3 + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.MAC_CONTEXT, (short) 0, (short) KMCose.MAC_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_MAC0 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unprotectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @param tag Bstr pointer which holds the tag value. + * @return KMArray instance of COSE_MAC0 object. + */ + public static short constructCoseMac0( + short protectedHeader, short unprotectedHeader, short payload, short tag) { + // Construct Cose_MAC0 + // COSE_Mac0 = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // tag : bstr, + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, tag); + // Do encode. + return arrPtr; + } + + /** + * Constructs the COSE_Signature structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the aad. + * @param payload Bstr pointer which holds the payload. + * @return KMArray instance of COSE_Signature object. + */ + public static short constructCoseSignStructure( + short protectedHeader, short extAad, short payload) { + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.SIGNATURE1_CONTEXT, (short) 0, (short) KMCose.SIGNATURE1_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_Sign1 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unProtectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload. + * @param signature Bstr pointer which holds the signature. + * @return KMArray instance of COSE_Sign1 object. + */ + public static short constructCoseSign1( + short protectedHeader, short unProtectedHeader, short payload, short signature) { + // COSE_Sign = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // signatures : [+ COSE_Signature] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, signature); + return arrPtr; + } + + /** + * Constructs array based on the tag values provided. + * + * @param tag array of tag values to be constructed. + * @param includeTestMode flag which indicates if TEST_COSE_KEY should be included or not. + * @return instance of KMArray. + */ + private static short handleCosePairTags( + short[] tag, short[] keyValues, short valueIndex, boolean includeTestMode) { + short index = 0; + // var is used to calculate the length of the array. + short var = 0; + short tagLen = (short) tag.length; + // var is used to calculate the length of the array. + while (index < tagLen) { + if (keyValues[index] != KMType.INVALID_VALUE) { + keyValues[(short) (index + valueIndex)] = + buildCosePairTag((byte) tag[index], keyValues[index]); + var++; + } + index++; + } + var += includeTestMode ? 1 : 0; + short arrPtr = KMArray.instance(var); + index = 0; + // var is used to index the array. + var = 0; + while (index < tagLen) { + if (keyValues[(short) (index + valueIndex)] != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).add(var++, keyValues[(short) (index + valueIndex)]); + } + index++; + } + return arrPtr; + } + + /** + * Constructs the COSE_sign1 payload for certificate. + * + * @param issuer instance of KMCosePairTextStringTag which contains issuer value. + * @param subject instance of KMCosePairTextStringTag which contains subject value. + * @param subPublicKey instance of KMCosePairByteBlobTag which contains encoded KMCoseKey. + * @param keyUsage instance of KMCosePairByteBlobTag which contains key usage value. + * @return instance of KMArray. + */ + public static short constructCoseCertPayload( + short issuer, short subject, short subPublicKey, short keyUsage) { + short certPayload = KMArray.instance((short) 4); + KMArray.cast(certPayload).add((short) 0, issuer); + KMArray.cast(certPayload).add((short) 1, subject); + KMArray.cast(certPayload).add((short) 2, subPublicKey); + KMArray.cast(certPayload).add((short) 3, keyUsage); + certPayload = KMCoseCertPayload.instance(certPayload); + KMCoseCertPayload.cast(certPayload).canonicalize(); + return certPayload; + } + + /** + * Construct headers structure. Headers can be part of COSE_Sign1, COSE_Encrypt, COSE_Mac0 and + * COSE_Key. + * + * @param alg instance of either KMNInteger or KMInteger, based on the sign of algorithm value. + * @param keyId instance of KMByteBlob which contains the key identifier. + * @param iv instance of KMByteblob which contains the iv buffer. + * @param ephemeralKey instance of KMCoseKey. + * @return instance of KMCoseHeaders. + */ + public static short constructHeaders( + short[] buff, short alg, short keyId, short iv, short ephemeralKey) { + buff[0] = alg; + buff[1] = keyId; + buff[2] = iv; + buff[3] = ephemeralKey; + for (short i = 4; i < 8; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short ptr = handleCosePairTags(COSE_HEADER_LABELS, buff, (short) 4, false); + ptr = KMCoseHeaders.instance(ptr); + KMCoseHeaders.cast(ptr).canonicalize(); + return ptr; + } + + /** + * Construct Recipients structure for COSE_Encrypt message. + * + * @param protectedHeaders instance of KMByteBlob which contains encoded KMCoseHeaders. + * @param unprotectedHeaders instance of KMCoseHeaders. + * @param cipherText instance of KMSimple + * @return instance of KMArray. + */ + public static short constructRecipientsStructure( + short protectedHeaders, short unprotectedHeaders, short cipherText) { + // recipients : [+COSE_recipient] + // COSE_recipient = [ + // Headers, + // ciphertext : bstr / nil, + // ? recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeaders); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeaders); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + + short recipientsArrayPtr = KMArray.instance((short) 1); + KMArray.cast(recipientsArrayPtr).add((short) 0, arrPtr); + return recipientsArrayPtr; + } + + /** + * Construct Encrypt structure required for COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param aad instance of KMByteBlob. + * @return instance of KMArray. + */ + public static short constructCoseEncryptStructure(short protectedHeader, short aad) { + // Enc_structure = [ + // context : "Encrypt" / "Encrypt0" / "Enc_Recipient" / + // "Mac_Recipient" / "Rec_Recipient", + // protected : empty_or_serialized_map, + // external_aad : bstr + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.ENCRYPT_CONTEXT, (short) 0, (short) KMCose.ENCRYPT_CONTEXT.length)); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, aad); + return arrPtr; + } + + /** + * Constructs COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param unProtectedHeader instance of KMCoseHeaders. + * @param cipherText instance of KMByteBlob containing the cipher text. + * @param recipients instance of KMArray containing the recipients instance + * @return instance of KMArray. + */ + public static short constructCoseEncrypt( + short protectedHeader, short unProtectedHeader, short cipherText, short recipients) { + // COSE_Encrypt = [ + // protectedHeader, + // unprotectedHeader, + // ciphertext : bstr / nil, + // recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_ENCRYPT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, recipients); + return arrPtr; + } + + /** + * Constructs the instance of KMCosePair*Tag. + * + * @param key value of the key. + * @param valuePtr instance of one of KMType. + * @return instance of KMCosePair*Value object. + */ + public static short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getType(valuePtr); + short keyPtr; + if (key < 0) { + keyPtr = KMNInteger.uint_8(key); + } else { + keyPtr = KMInteger.uint_8(key); + } + switch (type) { + case KMType.INTEGER_TYPE: + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + case KMType.NEG_INTEGER_TYPE: + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + case KMType.BYTE_BLOB_TYPE: + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + case KMType.TEXT_STRING_TYPE: + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + case KMType.COSE_KEY_TYPE: + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + /** + * Constructs a CoseKey with the provided input paramters. + * + * @param keyType Instance of the identification of the key type. + * @param keyId Instance of key identification value. + * @param keyAlg Instance of the algorithm that is used with this key. + * @param keyOps Instance of the operation that this key is used for. + * @param curve Instance of the EC curve that is used with this key. + * @param pubKey Buffer containing the public key. + * @param pubKeyOff Start offset of the buffer. + * @param pubKeyLen Length of the public key. + * @param privKeyPtr Instance of the private key. + * @param testMode Represents if key is used in test mode or production mode. + * @return Instance of the CoseKey structure. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short keyOps, + short curve, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + short privKeyPtr, + boolean testMode) { + if (pubKey[pubKeyOff] == 0x04) { // uncompressed format + pubKeyOff += 1; + pubKeyLen -= 1; + } + pubKeyLen = (short) (pubKeyLen / 2); + short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); + short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); + short coseKey = + constructCoseKey( + buff, keyType, keyId, keyAlg, keyOps, curve, xPtr, yPtr, privKeyPtr, testMode); + KMCoseKey.cast(coseKey).canonicalize(); + return coseKey; + } + + /** + * Constructs the cose key based on input parameters supplied. All the parameters must be + * instantiated from either KMInteger or KMNInteger or KMByteblob types. + * + * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. + * @param keyId instance of KMByteBlob which holds key identifier value. + * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. + * @param keyOps instance of KMInteger/KMNInteger which holds valid COSE key operations. + * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. + * @param pubX instance of KMByteBlob which holds EC public key's x value. + * @param pubY instance of KMByteBlob which holds EC public key's y value. + * @param priv instance of KMByteBlob which holds EC private value. + * @param includeTestKey flag which identifies whether to construct test key or production key. + * @return instance of the KMCoseKey object. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short keyOps, + short curve, + short pubX, + short pubY, + short priv, + boolean includeTestKey) { + short valueIndex = 8; + buff[0] = keyType; + buff[1] = keyId; + buff[2] = keyAlg; + buff[3] = keyOps; + buff[4] = curve; + buff[5] = pubX; + buff[6] = pubY; + buff[7] = priv; + for (short i = valueIndex; i < 16; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short arrPtr = handleCosePairTags(COSE_KEY_LABELS, buff, valueIndex, includeTestKey); + if (includeTestKey) { + short testKey = + KMCosePairSimpleValueTag.instance( + KMNInteger.uint_32(KMCose.COSE_TEST_KEY, (short) 0), + KMSimpleValue.instance(KMSimpleValue.NULL)); + KMArray.cast(arrPtr).add((short) (KMArray.cast(arrPtr).length() - 1), testKey); + } + arrPtr = KMCoseKey.instance(arrPtr); + KMCoseKey.cast(arrPtr).canonicalize(); + return arrPtr; + } + + /** + * Constructs key derivation context which is required to compute HKDF. + * + * @param publicKeyA public key buffer from the first party. + * @param publicKeyAOff start position of the public key buffer from first party. + * @param publicKeyALen length of the public key buffer from first party. + * @param publicKeyB public key buffer from the second party. + * @param publicKeyBOff start position of the public key buffer from second party. + * @param publicKeyBLen length of the public key buffer from second party. + * @param senderIsA true if caller is first party, false if caller is second party. + * @return instance of KMArray. + */ + public static short constructKdfContext( + byte[] publicKeyA, + short publicKeyAOff, + short publicKeyALen, + byte[] publicKeyB, + short publicKeyBOff, + short publicKeyBLen, + boolean senderIsA) { + short index = 0; + // Prepare sender info + short senderInfo = KMArray.instance((short) 3); + KMArray.cast(senderInfo) + .add(index++, KMByteBlob.instance(client, (short) 0, (short) client.length)); + KMArray.cast(senderInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(senderInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen) + : KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen)); + + // Prepare recipient info + index = 0; + short recipientInfo = KMArray.instance((short) 3); + KMArray.cast(recipientInfo) + .add(index++, KMByteBlob.instance(server, (short) 0, (short) server.length)); + KMArray.cast(recipientInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(recipientInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen) + : KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen)); + + // supply public info + index = 0; + short publicInfo = KMArray.instance((short) 2); + KMArray.cast(publicInfo).add(index++, KMInteger.uint_16(AES_GCM_KEY_SIZE_BITS)); + KMArray.cast(publicInfo).add(index, KMByteBlob.instance((short) 0)); + + // construct kdf context + index = 0; + short arrPtr = KMArray.instance((short) 4); + KMArray.cast(arrPtr).add(index++, KMInteger.uint_8(COSE_ALG_AES_GCM_256)); + KMArray.cast(arrPtr).add(index++, senderInfo); + KMArray.cast(arrPtr).add(index++, recipientInfo); + KMArray.cast(arrPtr).add(index, publicInfo); + + return arrPtr; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java new file mode 100644 index 0000000..fff9cf8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java @@ -0,0 +1,136 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseCertPayload represents the COSE_Sign1 payload for each certificate in BCC. The supported + * key types are KMInteger, KMNInteger and the supported value types are KMByteBlob and + * KMTextString. It corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short + * arrayPtr } where arrayPtr is a pointer to array with any KMCosePairTagType subtype instances. + */ +public class KMCoseCertPayload extends KMCoseMap { + + private static KMCoseCertPayload prototype; + + private KMCoseCertPayload() {} + + private static KMCoseCertPayload proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseCertPayload(); + } + instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairTextStringTag.exp()); + arr.add((short) 1, KMCosePairByteBlobTag.exp()); + return KMCoseCertPayload.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_CERT_PAYLOAD_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseCertPayload cast(short ptr) { + if (heap[ptr] != COSE_CERT_PAYLOAD_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + keyPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getSubjectPublicKey() { + return getValueType( + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 2), // LSB + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 0) // MSB (Significant) + ); + } + + public short getSubject() { + return getValueType(KMCose.SUBJECT, KMType.INVALID_VALUE); + } + + public short getIssuer() { + return getValueType(KMCose.ISSUER, KMType.INVALID_VALUE); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java new file mode 100644 index 0000000..0e722d2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java @@ -0,0 +1,203 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseHeaders represents headers section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-3. The supported key types are KMInteger, + * KMNInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMCoseKey. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseHeaders extends KMCoseMap { + + private static KMCoseHeaders prototype; + + private KMCoseHeaders() {} + + private static KMCoseHeaders proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseHeaders(); + } + instanceTable[KM_COSE_HEADERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + // CoseKey is internally an Array so evaluate it separately. + short coseKeyValueExp = KMCosePairCoseKeyTag.exp(); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, coseKeyValueExp); + return KMCoseHeaders.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_HEADERS_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseHeaders cast(short ptr) { + if (heap[ptr] != COSE_HEADERS_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_HEADERS_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key) { + short index = 0; + short len = length(); + short arr = getVals(); + short tagType; + short valPtr = 0; + short keyPtr; + boolean found = false; + while (index < len) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + keyPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_LABEL_KEYID); + } + + public short getCoseKey() { + return getValueType(KMCose.COSE_LABEL_COSE_KEY); + } + + public short getIV() { + return getValueType(KMCose.COSE_LABEL_IV); + } + + public short getAlgorithm() { + return getValueType(KMCose.COSE_LABEL_ALGORITHM); + } + + public boolean isDataValid(short[] buff, short alg, short keyIdPtr) { + short bufLen = 4; + buff[0] = KMCose.COSE_LABEL_ALGORITHM; + buff[1] = alg; + buff[2] = KMCose.COSE_LABEL_KEYID; + buff[3] = keyIdPtr; + boolean valid = false; + short value; + short ptr; + short tagIndex = 0; + while (tagIndex < bufLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex]); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + default: + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java new file mode 100644 index 0000000..d1a9f36 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -0,0 +1,253 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseKey represents COSE_Key section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-7 The supported key types are KMNInteger, + * KMInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMSimpleValue. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseKey extends KMCoseMap { + + private static KMCoseKey prototype; + + private KMCoseKey() {} + + private static KMCoseKey proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseKey(); + } + instanceTable[KM_COSE_KEY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, KMCosePairSimpleValueTag.exp()); + return KMCoseKey.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_KEY_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseKey cast(short ptr) { + if (heap[ptr] != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_KEY_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_KEY_KEY_ID, KMType.INVALID_VALUE); + } + + public short getEcdsa256PublicKey(byte[] pubKey, short pubKeyOff) { + short baseOffset = pubKeyOff; + pubKey[pubKeyOff] = (byte) 0x04; // uncompressed. + pubKeyOff++; + short ptr = getValueType(KMCose.COSE_KEY_PUBKEY_X, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + ptr = getValueType(KMCose.COSE_KEY_PUBKEY_Y, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + return (short) (pubKeyOff - baseOffset); + } + + public short getPrivateKey(byte[] priv, short privOff) { + short ptr = getValueType(KMCose.COSE_KEY_PRIV_KEY, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + priv, + privOff, + KMByteBlob.cast(ptr).length()); + return KMByteBlob.cast(ptr).length(); + } + + public boolean isTestKey() { + short ptr = + getValueType( + Util.getShort(KMCose.COSE_TEST_KEY, (short) 2), // LSB + Util.getShort(KMCose.COSE_TEST_KEY, (short) 0) // MSB (Significant) + ); + boolean isTestKey = false; + if (ptr != 0) { + isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + } + return isTestKey; + } + + /** + * Verifies the KMCoseKey values against the input values. + * + * @param keyType value of the key type + * @param keyIdPtr instance of KMByteBlob containing the key id. + * @param keyAlg value of the algorithm. + * @param keyOps value of the key operations. + * @param curve value of the curve. + * @return true if valid, otherwise false. + */ + public boolean isDataValid( + short[] buff, short keyType, short keyIdPtr, short keyAlg, short keyOps, short curve) { + short buffLen = 10; + buff[0] = KMCose.COSE_KEY_KEY_TYPE; + buff[1] = keyType; + buff[2] = KMCose.COSE_KEY_KEY_ID; + buff[3] = keyIdPtr; + buff[4] = KMCose.COSE_KEY_ALGORITHM; + buff[5] = keyAlg; + buff[6] = KMCose.COSE_KEY_KEY_OPS; + buff[7] = keyOps; + buff[8] = KMCose.COSE_KEY_CURVE; + buff[9] = curve; + boolean valid = false; + short ptr; + short tagIndex = 0; + short value; + while (tagIndex < buffLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex], KMType.INVALID_VALUE); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java new file mode 100644 index 0000000..5d373a7 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java @@ -0,0 +1,171 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class represents either a Cose_key or Cose headers as defined in + * https://datatracker.ietf.org/doc/html/rfc8152 This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * KMCoseHeaders and KMCoseKey implements this class. + */ +public abstract class KMCoseMap extends KMType { + + public static byte[] scratchpad; + + /** + * This function creates an instance of either KMCoseHeaders or KMCoseKey based on the type + * information provided. + * + * @param typePtr type information of the underlying KMType. + * @param arrPtr instance of KMArray. + * @return instance type of either KMCoseHeaders or KMCoseKey. + */ + public static short createInstanceFromType(short typePtr, short arrPtr) { + short mapType = KMType.getType(typePtr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.instance(arrPtr); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.instance(arrPtr); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.instance(arrPtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + public static short getVals(short ptr) { + short mapType = KMType.getType(ptr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.cast(ptr).getVals(); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.cast(ptr).getVals(); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.cast(ptr).getVals(); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private static short getKey(short tagPtr) { + short tagType = KMCosePairTagType.getTagValueType(tagPtr); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return KMCosePairByteBlobTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return KMCosePairIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return KMCosePairNegIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return KMCosePairSimpleValueTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return KMCosePairCoseKeyTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return KMCosePairTextStringTag.cast(tagPtr).getKeyPtr(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + private static void createScratchBuffer() { + if (scratchpad == null) { + scratchpad = JCSystem.makeTransientByteArray((short) 120, JCSystem.CLEAR_ON_RESET); + } + } + + protected static void canonicalize(short arr) { + canonicalize(arr, KMArray.cast(arr).length()); + } + + private static void swap(short ptr, short firstIndex, short secondIndex) { + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + KMArray.cast(ptr).swap(firstIndex, secondIndex); + } else { + KMMap.cast(ptr).swap(firstIndex, secondIndex); + } + } + + private static boolean compareAndSwap(short ptr, short index) { + short firstKey; + short secondKey; + short firstKeyLen; + short secondKeyLen; + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + firstKey = getKey(KMArray.cast(ptr).get(index)); + secondKey = getKey(KMArray.cast(ptr).get((short) (index + 1))); + } else { // Map + firstKey = KMMap.cast(ptr).getKey(index); + secondKey = KMMap.cast(ptr).getKey((short) (index + 1)); + } + firstKeyLen = + KMKeymasterApplet.encoder.encode( + firstKey, scratchpad, (short) 0, (short) scratchpad.length); + secondKeyLen = + KMKeymasterApplet.encoder.encode( + secondKey, scratchpad, firstKeyLen, (short) scratchpad.length); + if ((firstKeyLen > secondKeyLen) + || ((firstKeyLen == secondKeyLen) + && (0 + < Util.arrayCompare( + scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) { + swap(ptr, index, (short) (index + 1)); + return true; + } + return false; + } + + /** + * Canonicalizes using bubble sort. + * + * @param ptr instance pointer of either array or map. + * @param length length of the array or map instance. + */ + public static void canonicalize(short ptr, short length) { + short index = 0; + short innerIndex = 0; + createScratchBuffer(); + boolean swapped; + while (index < length) { + swapped = false; + innerIndex = 0; + while (innerIndex < (short) (length - index - 1)) { + swapped |= compareAndSwap(ptr, innerIndex); + innerIndex++; + } + if (!swapped) { + break; + } + index++; + } + } + + public abstract short getVals(); + + public abstract short length(); + + public abstract void canonicalize(); +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java new file mode 100644 index 0000000..04c3abe --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java @@ -0,0 +1,137 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairByteBlobTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMByteBlob type. struct{byte TAG_TYPE; short length; struct{short BYTE_BLOB_TYPE; short + * key; short value}}. + */ +public class KMCosePairByteBlobTag extends KMCosePairTagType { + + public static Object[] keys; + private static KMCosePairByteBlobTag prototype; + + private KMCosePairByteBlobTag() {} + + private static KMCosePairByteBlobTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairByteBlobTag(); + } + instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMByteBlob.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(keyPtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairByteBlobTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static void createKeys() { + if (keys == null) { + keys = + new Object[] { + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_X}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_Y}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PRIV_KEY}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_IV}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_KEYID}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_KEY_ID}, + (Object) KMCose.SUBJECT_PUBLIC_KEY, + (Object) KMCose.KEY_USAGE + }; + } + } + + public static boolean isKeyValueValid(short keyPtr) { + createKeys(); + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short index = 0; + while (index < (short) keys.length) { + if (0 + == Util.arrayCompare( + (byte[]) keys[index], + (short) 0, + heap, + offset, + (short) ((byte[]) keys[index]).length)) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return BYTE_BLOB_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java new file mode 100644 index 0000000..be853de --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java @@ -0,0 +1,89 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairCoseKeyTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMCOseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairCoseKeyTag extends KMCosePairTagType { + + public static final byte[] keys = {KMCose.COSE_LABEL_COSE_KEY}; + private static KMCosePairCoseKeyTag prototype; + + private KMCosePairCoseKeyTag() {} + + private static KMCosePairCoseKeyTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairCoseKeyTag(); + } + instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMCoseKey.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairCoseKeyTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return COSE_KEY_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java new file mode 100644 index 0000000..ea052a6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java @@ -0,0 +1,92 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMInteger type. struct{byte TAG_TYPE; short length; struct{short INT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairIntegerTag extends KMCosePairTagType { + + private static KMCosePairIntegerTag prototype; + + private KMCosePairIntegerTag() {} + + private static KMCosePairIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairIntegerTag(); + } + instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMInteger.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java new file mode 100644 index 0000000..7f01202 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java @@ -0,0 +1,92 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairNegIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMNInteger type. struct{byte TAG_TYPE; short length; struct{short NINT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairNegIntegerTag extends KMCosePairTagType { + + private static KMCosePairNegIntegerTag prototype; + + private KMCosePairNegIntegerTag() {} + + private static KMCosePairNegIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairNegIntegerTag(); + } + instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMNInteger.exp()); + return ptr; + } + + public static KMCosePairNegIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (NEG_INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMNInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public short getValueType() { + return NEG_INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java new file mode 100644 index 0000000..a0d7da8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java @@ -0,0 +1,76 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairSimpleValueTag represents a key-value type, where key can be KMInteger or KMNInteger + * and value is KMSimpleValue type. struct{byte TAG_TYPE; short length; struct{short + * SIMPLE_VALUE_TYPE; short key; short value}}. + */ +public class KMCosePairSimpleValueTag extends KMCosePairTagType { + + private static KMCosePairSimpleValueTag prototype; + + private KMCosePairSimpleValueTag() {} + + private static KMCosePairSimpleValueTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairSimpleValueTag(); + } + instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMSimpleValue.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMSimpleValue.cast(valuePtr).getValue())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairSimpleValueTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return SIMPLE_VALUE_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java new file mode 100644 index 0000000..f3fc76e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -0,0 +1,258 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * This class represents the COSE_Key as defined in + * https://datatracker.ietf.org/doc/html/rfc8152#section-7. This is basically a map containing key + * value pairs. The label for the key can be (uint / int / tstr) and the value can be of any type. + * But this class is confined to support only key and value types which are required for remote key + * provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr) only + * are supported. The structure representing all the sub classes of KMCosePairTagType is as follows: + * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), + * Value(2 bytes). Key can be either KMInteger or KMNInteger and Value can be either KMIntger or + * KMNinteger or KMSimpleValue or KMByteBlob or KMTextString or KMCoseKey. Each subclass of + * KMCosePairTagType is named after their corresponding value type of the Cose pair. + */ +public abstract class KMCosePairTagType extends KMType { + + /** + * Below table represents the allowed values for a key. The maximum length of the key can be 4 + * bytes so each key is represented as 4 bytes. The allowed values are placed next to their + * corresponding key. + */ + public static Object[] allowedKeyPairs; + + private static void createAllowedKeyPairs() { + if (allowedKeyPairs == null) { + allowedKeyPairs = + new Object[] { + // Key type + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_TYPE}, + (Object) new byte[] {KMCose.COSE_KEY_TYPE_EC2, KMCose.COSE_KEY_TYPE_SYMMETRIC_KEY}, + // Key Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + KMCose.COSE_ALG_ES256 + }, + // Key operations + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_OPS}, + (Object) + new byte[] { + KMCose.COSE_KEY_OP_SIGN, + KMCose.COSE_KEY_OP_VERIFY, + KMCose.COSE_KEY_OP_ENCRYPT, + KMCose.COSE_KEY_OP_DECRYPT + }, + // Key Curve + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_CURVE}, + (Object) new byte[] {KMCose.COSE_ECCURVE_256}, + // Header Label Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_LABEL_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256 + }, + // Test Key + KMCose.COSE_TEST_KEY, + (Object) new byte[] {KMSimpleValue.NULL}, + }; + } + } + + /** + * Validates the key and the values corresponding to key. + * + * @param key Buffer containing the key. + * @param keyOff Offset in the buffer from where key starts. + * @param keyLen Length of the key buffer. + * @param value Value corresponding to the key. + * @return true if key pair is valid, otherwise false. + */ + public static boolean isKeyPairValid(byte[] key, short keyOff, short keyLen, short value) { + short index = 0; + short valueIdx; + byte[] values; + boolean valid = false; + createAllowedKeyPairs(); + while (index < allowedKeyPairs.length) { + valueIdx = 0; + if (isEqual( + (byte[]) allowedKeyPairs[index], + (short) 0, + (short) ((byte[]) allowedKeyPairs[index]).length, + key, + keyOff, + keyLen)) { + values = (byte[]) allowedKeyPairs[(short) (index + 1)]; + while (valueIdx < values.length) { + if (values[valueIdx] == (byte) value) { + valid = true; + break; + } + valueIdx++; + } + if (valid) { + break; + } + } + index += (short) 2; + } + return valid; + } + + /** + * Compares two key buffers. + * + * @param key1 First buffer containing the key. + * @param offset1 Offset of the first buffer. + * @param length1 Length of the first buffer. + * @param key2 Second buffer containing the key. + * @param offset2 Offset of the second buffer. + * @param length2 Length of the second buffer. + * @return true if both keys are equal, otherwise false. + */ + private static boolean isEqual( + byte[] key1, short offset1, short length1, byte[] key2, short offset2, short length2) { + if (length1 != length2) { + return false; + } + return (0 == KMInteger.unsignedByteArrayCompare(key1, offset1, key2, offset2, length1)); + } + + /** + * Returns the short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + /** + * Returns the significant short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueSignificantShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getSignificantShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getSignificantShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { + short type = KMType.getType(keyPtr); + if (type == INTEGER_TYPE) { + KMInteger.cast(keyPtr).getValue(dest, offset, len); + } else if (type == NEG_INTEGER_TYPE) { + KMNInteger.cast(keyPtr).getValue(dest, offset, len); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + /** + * Returns the key offset from the key pointer. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return offset from where the key starts. + */ + public static short getKeyStartOffset(short keyPtr) { + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return offset; + } + + /** + * Returns the key length. + * + * @param keyPtr pointer to either KMInteger/KMInteger. + * @return length of the key. + */ + public static short getKeyLength(short keyPtr) { + short type = KMType.getType(keyPtr); + short len = 0; + if (type == INTEGER_TYPE) { + len = KMInteger.cast(keyPtr).length(); + } else if (type == NEG_INTEGER_TYPE) { + len = KMNInteger.cast(keyPtr).length(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + /** + * This function returns one of COSE_KEY_TAG_*_VALUE_TYPE tag information. + * + * @param ptr Pointer to one of the KMCoseKey*Value class. + * @return Tag value type. + */ + public static short getTagValueType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + /** + * This function returns the key pointer. + * + * @return key pointer. + */ + public abstract short getKeyPtr(); + + /** + * This function returns the value pointer. + * + * @return value pointer. + */ + public abstract short getValuePtr(); +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java new file mode 100644 index 0000000..99506b6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java @@ -0,0 +1,91 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairTextStringTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMTextString type. struct{byte TAG_TYPE; short length; struct{short TXT_STR_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairTextStringTag extends KMCosePairTagType { + + public static final byte[] keys = { + KMCose.ISSUER, KMCose.SUBJECT, + }; + private static KMCosePairTextStringTag prototype; + + private KMCosePairTextStringTag() {} + + private static KMCosePairTextStringTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairTextStringTag(); + } + instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMTextString.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairTextStringTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return TEXT_STRING_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java new file mode 100644 index 0000000..10c7d29 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -0,0 +1,774 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +public class KMDecoder { + + // major types + private static final short UINT_TYPE = 0x00; + private static final short NEG_INT_TYPE = 0x20; + private static final short BYTES_TYPE = 0x40; + private static final short TSTR_TYPE = 0x60; + private static final short ARRAY_TYPE = 0x80; + private static final short MAP_TYPE = 0xA0; + private static final short SIMPLE_VALUE_TYPE = 0xE0; + private static final short SEMANTIC_TAG_TYPE = 0xC0; + + // masks + private static final short ADDITIONAL_MASK = 0x1F; + private static final short MAJOR_TYPE_MASK = 0xE0; + + // value length + private static final short UINT8_LENGTH = 0x18; + private static final short UINT16_LENGTH = 0x19; + private static final short UINT32_LENGTH = 0x1A; + private static final short UINT64_LENGTH = 0x1B; + + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte TAG_KEY_OFFSET = 4; + private Object[] bufferRef; + private short[] scratchBuf; + + public KMDecoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[TAG_KEY_OFFSET] = (short) 0; + } + + public short decode(short expression, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + return decode(expression); + } + + public short decodeArray(short exp, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short expLength = KMArray.cast(exp).length(); + if (payloadLength > expLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short index = 0; + short obj; + short type; + short arrPtr = KMArray.instance(payloadLength); + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + return arrPtr; + } + + private short decode(short exp) { + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + return decodeByteBlob(exp); + case KMType.TEXT_STRING_TYPE: + return decodeTstr(exp); + case KMType.INTEGER_TYPE: + return decodeInteger(exp); + case KMType.SIMPLE_VALUE_TYPE: + return decodeSimpleValue(exp); + case KMType.SEMANTIC_TAG_TYPE: + return decodeSemanticTagValue(exp); + case KMType.NEG_INTEGER_TYPE: + return decodeNegInteger(exp); + case KMType.ARRAY_TYPE: + return decodeArray(exp); + case KMType.MAP_TYPE: + return decodeMap(exp); + case KMType.ENUM_TYPE: + return decodeEnum(exp); + case KMType.KEY_PARAM_TYPE: + return decodeKeyParam(exp); + case KMType.KEY_CHAR_TYPE: + return decodeKeyChar(exp); + case KMType.VERIFICATION_TOKEN_TYPE: + return decodeVerificationToken(exp); + case KMType.HMAC_SHARING_PARAM_TYPE: + return decodeHmacSharingParam(exp); + case KMType.HW_AUTH_TOKEN_TYPE: + return decodeHwAuthToken(exp); + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + return decodeCoseMap(exp); + case KMType.COSE_PAIR_TAG_TYPE: + short tagValueType = KMCosePairTagType.getTagValueType(exp); + return decodeCosePairTag(tagValueType, exp); + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + return decodeTag(tagType, exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + return decodeBignumTag(exp); + case KMType.BYTES_TAG: + return decodeBytesTag(exp); + case KMType.BOOL_TAG: + return decodeBoolTag(exp); + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + return decodeIntegerTag(exp); + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + return decodeIntegerArrayTag(exp); + case KMType.ENUM_TAG: + return decodeEnumTag(exp); + case KMType.ENUM_ARRAY_TAG: + return decodeEnumArrayTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeVerificationToken(short exp) { + short vals = decode(KMVerificationToken.cast(exp).getVals()); + return KMVerificationToken.instance(vals); + } + + private short decodeHwAuthToken(short exp) { + short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + return KMHardwareAuthToken.instance(vals); + } + + private short decodeHmacSharingParam(short exp) { + short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + return KMHmacSharingParameters.instance(vals); + } + + private short decodeKeyChar(short exp) { + short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + return KMKeyCharacteristics.instance(vals); + } + + private short decodeCosePairKey(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short keyPtr = (short) 0; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) == UINT_TYPE) { + keyPtr = decodeInteger(exp); + } else if ((buffer[startOff] & MAJOR_TYPE_MASK) == NEG_INT_TYPE) { + keyPtr = decodeNegInteger(exp); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return keyPtr; + } + + private short decodeCosePairSimpleValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairSimpleValueTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairSimpleValueTag.cast(exp).getValuePtr()); + return KMCosePairSimpleValueTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairIntegerValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairIntegerTag.cast(exp).getValuePtr()); + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairNegIntegerTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairNegIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairNegIntegerTag.cast(exp).getValuePtr()); + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairTxtStringTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairTextStringTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairTextStringTag.cast(exp).getValuePtr()); + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairCoseKeyTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairCoseKeyTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairCoseKeyTag.cast(exp).getValuePtr()); + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairByteBlobTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairByteBlobTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairByteBlobTag.cast(exp).getValuePtr()); + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + } + + private short peekCosePairTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE + && (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + short additionalMask = (short) (buffer[startOff] & ADDITIONAL_MASK); + short increment = 0; + if (additionalMask < UINT8_LENGTH) { + increment++; + } else if (additionalMask == UINT8_LENGTH) { + increment += 2; + } else if (additionalMask == UINT16_LENGTH) { + increment += 3; + } else if (additionalMask == UINT32_LENGTH) { + increment += 5; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short majorType = (short) (buffer[(short) (startOff + increment)] & MAJOR_TYPE_MASK); + short tagValueType = 0; + if (majorType == BYTES_TYPE) { + tagValueType = KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE; + } else if (majorType == UINT_TYPE) { + tagValueType = KMType.COSE_PAIR_INT_TAG_TYPE; + } else if (majorType == NEG_INT_TYPE) { + tagValueType = KMType.COSE_PAIR_NEG_INT_TAG_TYPE; + } else if (majorType == MAP_TYPE) { + tagValueType = KMType.COSE_PAIR_COSE_KEY_TAG_TYPE; + } else if (majorType == SIMPLE_VALUE_TYPE) { + tagValueType = KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE; + } else if (majorType == TSTR_TYPE) { + tagValueType = KMType.COSE_PAIR_TEXT_STR_TAG_TYPE; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return tagValueType; + } + + private short decodeCosePairTag(short tagValueType, short exp) { + switch (tagValueType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return decodeCosePairByteBlobTag(exp); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return decodeCosePairNegIntegerTag(exp); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return decodeCosePairIntegerValueTag(exp); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return decodeCosePairSimpleValueTag(exp); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return decodeCosePairCoseKeyTag(exp); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return decodeCosePairTxtStringTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeCoseMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // get allowed key pairs + short allowedKeyPairs = KMCoseMap.getVals(exp); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedKeyPairs).length(); + short index = 0; + boolean tagFound; + short tagInd; + short cosePairTagType; + short tagClass; + short allowedType; + short obj; + + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + cosePairTagType = peekCosePairTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + allowedType = KMCosePairTagType.getTagValueType(tagClass); + if (allowedType == cosePairTagType) { + obj = decode(tagClass); + KMArray.cast(vals).add(index, obj); + tagFound = true; + break; + } + tagInd++; + } + if (!tagFound) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else { + index++; + } + } + return KMCoseMap.createInstanceFromType(exp, vals); + } + + private short decodeKeyParam(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // allowed tags + short allowedTags = KMKeyParameters.cast(exp).getVals(); + short tagRule = KMArray.cast(allowedTags).get((short) 0); + boolean ignoreInvalidTags = KMEnum.cast(tagRule).getVal() == KMType.IGNORE_INVALID_TAGS; + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedTags).length(); + short index = 0; + boolean tagFound; + short tagInd; + short tagType; + short tagClass; + short allowedType; + short obj; + short arrPos = 0; + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 1; + tagType = peekTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedTags).get(tagInd); + allowedType = KMTag.getTagType(tagClass); + // If it is part of allowed tags ... + if (tagType == allowedType) { + // then decodeByteBlob and add that to the array. + try { + tagFound = true; + obj = decode(tagClass); + KMArray.cast(vals).add(arrPos++, obj); + break; + } catch (KMException e) { + if (KMException.reason() == KMError.INVALID_TAG) { + if (!ignoreInvalidTags) { + KMException.throwIt(KMError.INVALID_TAG); + } + } else { + KMException.throwIt(KMException.reason()); + } + break; + } + } + tagInd++; + } + if (!tagFound) { + KMException.throwIt(KMError.INVALID_TAG); + } else { + index++; + } + } + KMArray.cast(vals).setLength(arrPos); + return KMKeyParameters.instance(vals); + } + + private short decodeEnumArrayTag(short exp) { + readTagKey(KMEnumArrayTag.cast(exp).getTagType()); + return KMEnumArrayTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerArrayTag(short exp) { + readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); + // the values are array of integers. + return KMIntegerArrayTag.instance( + KMIntegerArrayTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerTag(short exp) { + readTagKey(KMIntegerTag.cast(exp).getTagType()); + // the value is an integer + return KMIntegerTag.instance( + KMIntegerTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerTag.cast(exp).getValue())); + } + + private short decodeBytesTag(short exp) { + readTagKey(KMByteTag.cast(exp).getTagType()); + // The value must be byte blob + return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.cast(exp).getValue())); + } + + private short decodeBignumTag(short exp) { + readTagKey(KMBignumTag.cast(exp).getTagType()); + // The value must be byte blob + return KMBignumTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.cast(exp).getValue())); + } + + private short decodeMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + short mapPtr = KMMap.instance(payloadLength); + short index = 0; + short type; + short keyobj; + short valueobj; + while (index < payloadLength) { + type = KMMap.cast(exp).getKey(index); + keyobj = decode(type); + type = KMMap.cast(exp).getKeyValue(index); + valueobj = decode(type); + KMMap.cast(mapPtr).add(index, keyobj, valueobj); + index++; + } + return mapPtr; + } + + private short decodeArray(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short arrPtr = KMArray.instance(payloadLength); + short index = 0; + short type; + short obj; + // check whether array contains one type of objects or multiple types + if (KMArray.cast(exp).containedType() + == KMType.INVALID_VALUE) { // multiple types specified by expression. + if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.cast(exp).length() != payloadLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + } + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } else { // Array is a Vector containing objects of one type + type = KMArray.cast(exp).containedType(); + while (index < payloadLength) { + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } + return arrPtr; + } + + private short decodeEnumTag(short exp) { + readTagKey(KMEnumTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum Tag value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal = 0; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else if (len == UINT8_LENGTH) { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnumTag.instance(scratchBuf[TAG_KEY_OFFSET], enumVal); + } + + private short decodeBoolTag(short exp) { + readTagKey(KMBoolTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + incrementStartOff((short) 1); + return KMBoolTag.instance(scratchBuf[TAG_KEY_OFFSET]); + } + + private short decodeEnum(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); + } + + private short decodeSimpleValue(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byte addInfo = (byte) (buffer[startOff] & ADDITIONAL_MASK); + incrementStartOff((short) 1); + return KMSimpleValue.instance(addInfo); + } + + private short decodeSemanticTagValue(short exp) { + // Decode tag. + short tag = readMajorTypeWithInteger(exp, SEMANTIC_TAG_TYPE, UINT32_LENGTH); + // Decode value pointer. + short valuePtr = decode(KMSemanticTag.cast(exp).getValuePtr()); + return KMSemanticTag.instance(tag, valuePtr); + } + + private short readMajorTypeWithInteger(short exp, short majorType, short maxLimit) { + short inst; + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > maxLimit) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + if (len < UINT8_LENGTH) { + inst = KMInteger.uint_8((byte) (len & ADDITIONAL_MASK)); + } else if (len == UINT8_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 1); + incrementStartOff((short) 1); + } else if (len == UINT16_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 2); + incrementStartOff((short) 2); + } else if (len == UINT32_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 4); + incrementStartOff((short) 4); + } else { + inst = KMInteger.instance(buffer, startOff, (short) 8); + incrementStartOff((short) 8); + } + return inst; + } + + private short decodeInteger(short exp) { + return readMajorTypeWithInteger(exp, UINT_TYPE, UINT64_LENGTH); + } + + private short decodeNegIntegerValue(byte addInfo, byte[] buf, short startOffset) { + short inst; + short len = 0; + short scratchpad; + if (addInfo < UINT8_LENGTH) { + addInfo = (byte) (-1 - addInfo); + inst = KMNInteger.uint_8(addInfo); + } else { + switch (addInfo) { + case UINT8_LENGTH: + len = 1; + break; + case UINT16_LENGTH: + len = 2; + break; + case UINT32_LENGTH: + len = 4; + break; + case UINT64_LENGTH: + len = 8; + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // Do (-1 - N), as per cbor negative integer decoding rule. + // N is the integer value. + scratchpad = KMByteBlob.instance((short) (len * 3)); + byte[] input = KMByteBlob.cast(scratchpad).getBuffer(); + short offset = KMByteBlob.cast(scratchpad).getStartOff(); + Util.arrayFillNonAtomic(input, offset, len, (byte) -1); + Util.arrayCopyNonAtomic(buf, startOffset, input, (short) (offset + len), len); + KMUtils.subtract( + input, offset, (short) (offset + len), (short) (offset + 2 * len), (byte) len); + inst = KMNInteger.instance(input, (short) (offset + 2 * len), len); + incrementStartOff(len); + } + return inst; + } + + private short decodeNegInteger(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > UINT64_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + return decodeNegIntegerValue((byte) len, buffer, startOff); + } + + private short decodeTstr(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(TSTR_TYPE); + short inst = + KMTextString.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short decodeByteBlob(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(BYTES_TYPE); + short inst = + KMByteBlob.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short peekTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + if ((short) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return (short) + ((Util.makeShort(buffer[(short) (startOff + 1)], buffer[(short) (startOff + 2)])) + & KMType.TAG_TYPE_MASK); + } + + private void readTagKey(short expectedTagType) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + short tagType = readShort(); + scratchBuf[TAG_KEY_OFFSET] = readShort(); + if (tagType != expectedTagType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + // payload length cannot be more then 16 bits. + private short readMajorTypeWithPayloadLength(short majorType) { + short payloadLength; + byte val = readByte(); + if ((short) (val & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short lenType = (short) (val & ADDITIONAL_MASK); + if (lenType > UINT16_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (lenType < UINT8_LENGTH) { + payloadLength = lenType; + } else if (lenType == UINT8_LENGTH) { + payloadLength = (short) (readByte() & 0xFF); + } else { + payloadLength = readShort(); + } + return payloadLength; + } + + private short readShort() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short val = Util.makeShort(buffer[startOff], buffer[(short) (startOff + 1)]); + incrementStartOff((short) 2); + return val; + } + + private byte readByte() { + short startOff = scratchBuf[START_OFFSET]; + byte val = ((byte[]) bufferRef[0])[startOff]; + incrementStartOff((short) 1); + return val; + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] > scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short readKeyblobVersion(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + short arrayLen = readMajorTypeWithPayloadLength(ARRAY_TYPE); + if (arrayLen == 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short version = KMType.INVALID_VALUE; + try { + version = decodeInteger(KMInteger.exp()); + } catch (Exception e) { + // Fail to decode Integer. It can happen if it is an old KeyBlob. + } + return version; + } + + public short readCertificateChainHeaderLen(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + readMajorTypeWithPayloadLength(BYTES_TYPE); + return (short) (scratchBuf[START_OFFSET] - bufOffset); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java new file mode 100644 index 0000000..7405e06 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -0,0 +1,772 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +public class KMEncoder { + + // major types + private static final byte UINT_TYPE = 0x00; + private static final byte NEG_INT_TYPE = 0x20; + private static final byte BYTES_TYPE = 0x40; + private static final byte TSTR_TYPE = 0x60; + private static final byte ARRAY_TYPE = (byte) 0x80; + private static final byte MAP_TYPE = (byte) 0xA0; + private static final byte SIMPLE_VALUE_TYPE = (byte) 0xE0; + private static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + + // masks + private static final byte ADDITIONAL_MASK = 0x1F; + + // value length + private static final byte UINT8_LENGTH = (byte) 0x18; + private static final byte UINT16_LENGTH = (byte) 0x19; + private static final byte UINT32_LENGTH = (byte) 0x1A; + private static final byte UINT64_LENGTH = (byte) 0x1B; + private static final short TINY_PAYLOAD = 0x17; + private static final short SHORT_PAYLOAD = 0x100; + private static final byte STACK_SIZE = 50; + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte STACK_PTR_OFFSET = 4; + + private Object[] bufferRef; + private short[] scratchBuf; + private short[] stack; + + public KMEncoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + stack = JCSystem.makeTransientShortArray(STACK_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[STACK_PTR_OFFSET] = (short) 0; + } + + private void push(short objPtr) { + stack[scratchBuf[STACK_PTR_OFFSET]] = objPtr; + scratchBuf[STACK_PTR_OFFSET]++; + } + + private short pop() { + scratchBuf[STACK_PTR_OFFSET]--; + return stack[scratchBuf[STACK_PTR_OFFSET]]; + } + + private void encode(short obj) { + push(obj); + } + + /** + * This functions encodes the given object into the provider buffer space in cbor format. + * + * @param object Object to be encoded into cbor data. + * @param buffer Output where cbor data is copied. + * @param startOff is the start offset of the buffer. + * @param bufLen length of the buffer + * @param encoderOutLimitLen excepted encoded output length. + * @return length of the encoded buffer. + */ + public short encode( + short object, byte[] buffer, short startOff, short bufLen, short encoderOutLimitLen) { + scratchBuf[STACK_PTR_OFFSET] = 0; + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + if ((short) (startOff + encoderOutLimitLen) > bufLen) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + scratchBuf[LEN_OFFSET] = (short) (startOff + encoderOutLimitLen); + push(object); + encode(); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + public short encode(short object, byte[] buffer, short startOff, short bufLen) { + return encode(object, buffer, startOff, bufLen, (short) (bufLen - startOff)); + } + + // array{KMError.OK,Array{KMByteBlobs}} + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { + if (bufferStart > certStart) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + bufferRef[0] = certBuffer; + scratchBuf[START_OFFSET] = certStart; + scratchBuf[LEN_OFFSET] = (short) (certStart + 1); + // Byte Header + cert length + scratchBuf[START_OFFSET] -= getEncodedBytesLength(certLength); + // Array header - 1 elements i.e. 1 byte + scratchBuf[START_OFFSET]--; + if (scratchBuf[START_OFFSET] < bufferStart) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + bufferStart = scratchBuf[START_OFFSET]; + writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 elements + writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length + return bufferStart; + } + + private void encode() { + while (scratchBuf[STACK_PTR_OFFSET] > 0) { + short exp = pop(); + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + encodeByteBlob(exp); + break; + case KMType.TEXT_STRING_TYPE: + encodeTextString(exp); + break; + case KMType.INTEGER_TYPE: + encodeUnsignedInteger(exp); + break; + case KMType.SIMPLE_VALUE_TYPE: + encodeSimpleValue(exp); + break; + case KMType.NEG_INTEGER_TYPE: + encodeNegInteger(exp); + break; + case KMType.ARRAY_TYPE: + encodeArray(exp); + break; + case KMType.MAP_TYPE: + encodeMap(exp); + break; + case KMType.ENUM_TYPE: + encodeEnum(exp); + break; + case KMType.KEY_PARAM_TYPE: + encodeKeyParam(exp); + break; + case KMType.SEMANTIC_TAG_TYPE: + encodeSemanticTag(exp); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + encodeCoseMap(exp); + break; + case KMType.KEY_CHAR_TYPE: + encodeKeyChar(exp); + break; + case KMType.VERIFICATION_TOKEN_TYPE: + encodeVeriToken(exp); + break; + case KMType.HMAC_SHARING_PARAM_TYPE: + encodeHmacSharingParam(exp); + break; + case KMType.HW_AUTH_TOKEN_TYPE: + encodeHwAuthToken(exp); + break; + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + encodeTag(tagType, exp); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(exp); + encodeCosePairTag(cosePairTagType, exp); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + } + + private void encodeCosePairIntegerTag(short exp) { + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairIntTag.getValuePtr()); + encode(cosePairIntTag.getKeyPtr()); + } + + private void encodeCosePairByteBlobTag(short exp) { + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairByteBlobTag.getValuePtr()); + encode(cosePairByteBlobTag.getKeyPtr()); + } + + private void encodeCosePairCoseKeyTag(short exp) { + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairCoseKeyTag.getValuePtr()); + encode(cosePairCoseKeyTag.getKeyPtr()); + } + + private void encodeCosePairTextStringTag(short exp) { + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairTextStringTag.getValuePtr()); + encode(cosePairTextStringTag.getKeyPtr()); + } + + private void encodeCosePairSimpleValueTag(short exp) { + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairSimpleValueTag.getValuePtr()); + encode(cosePairSimpleValueTag.getKeyPtr()); + } + + private void encodeCosePairNegIntegerTag(short exp) { + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairNegIntegerTag.getValuePtr()); + encode(cosePairNegIntegerTag.getKeyPtr()); + } + + private void encodeCosePairTag(short tagType, short exp) { + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + encodeCosePairByteBlobTag(exp); + return; + case KMType.COSE_PAIR_INT_TAG_TYPE: + encodeCosePairIntegerTag(exp); + return; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + encodeCosePairNegIntegerTag(exp); + return; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + encodeCosePairSimpleValueTag(exp); + return; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + encodeCosePairTextStringTag(exp); + return; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + encodeCosePairCoseKeyTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + encodeBignumTag(exp); + return; + case KMType.BYTES_TAG: + encodeBytesTag(exp); + return; + case KMType.BOOL_TAG: + encodeBoolTag(exp); + return; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + encodeIntegerTag(exp); + return; + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + encodeIntegerArrayTag(exp); + return; + case KMType.ENUM_TAG: + encodeEnumTag(exp); + return; + case KMType.ENUM_ARRAY_TAG: + encodeEnumArrayTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeCoseMap(short obj) { + encodeAsMap(KMCoseMap.getVals(obj)); + } + + private void encodeKeyParam(short obj) { + encodeAsMap(KMKeyParameters.cast(obj).getVals()); + } + + private void encodeKeyChar(short obj) { + encode(KMKeyCharacteristics.cast(obj).getVals()); + } + + private void encodeVeriToken(short obj) { + encode(KMVerificationToken.cast(obj).getVals()); + } + + private void encodeHwAuthToken(short obj) { + encode(KMHardwareAuthToken.cast(obj).getVals()); + } + + private void encodeHmacSharingParam(short obj) { + encode(KMHmacSharingParameters.cast(obj).getVals()); + } + + private void encodeArray(short obj) { + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short subObj; + while (index >= 0) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + encode(subObj); + } + index--; + } + } + + public void encodeArrayOnlyLength(short arrLength, byte[] buffer, short offset, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = offset; + scratchBuf[LEN_OFFSET] = (short) (offset + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, length); + } + + private void encodeMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMMap.cast(obj).length()); + short len = KMMap.cast(obj).length(); + short index = (short) (len - 1); + while (index >= 0) { + encode(KMMap.cast(obj).getKeyValue(index)); + encode(KMMap.cast(obj).getKey(index)); + index--; + } + } + + private void encodeAsMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short inst; + while (index >= 0) { + inst = KMArray.cast(obj).get(index); + encode(inst); + index--; + } + } + + private void encodeIntegerArrayTag(short obj) { + writeTag(KMIntegerArrayTag.cast(obj).getTagType(), KMIntegerArrayTag.cast(obj).getKey()); + encode(KMIntegerArrayTag.cast(obj).getValues()); + } + + private void encodeEnumArrayTag(short obj) { + writeTag(KMEnumArrayTag.cast(obj).getTagType(), KMEnumArrayTag.cast(obj).getKey()); + encode(KMEnumArrayTag.cast(obj).getValues()); + } + + private void encodeIntegerTag(short obj) { + writeTag(KMIntegerTag.cast(obj).getTagType(), KMIntegerTag.cast(obj).getKey()); + encode(KMIntegerTag.cast(obj).getValue()); + } + + private void encodeBignumTag(short obj) { + writeTag(KMBignumTag.getTagType(obj), KMBignumTag.getKey(obj)); + encode(KMBignumTag.cast(obj).getValue()); + } + + private void encodeBytesTag(short obj) { + writeTag(KMByteTag.cast(obj).getTagType(), KMByteTag.cast(obj).getKey()); + encode(KMByteTag.cast(obj).getValue()); + } + + private void encodeBoolTag(short obj) { + writeTag(KMBoolTag.cast(obj).getTagType(), KMBoolTag.cast(obj).getKey()); + writeByteValue(KMBoolTag.cast(obj).getVal()); + } + + private void encodeEnumTag(short obj) { + writeTag(KMEnumTag.cast(obj).getTagType(), KMEnumTag.cast(obj).getKey()); + writeByteValue(KMEnumTag.cast(obj).getValue()); + } + + private void encodeEnum(short obj) { + writeByteValue(KMEnum.cast(obj).getVal()); + } + + private void encodeInteger(byte[] val, short len, short startOff, short majorType) { + // find out the most significant byte + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + if (diff == 0) { + writeByte((byte) (majorType | 0)); + } else if ((diff == 1) + && (val[(short) (startOff + msbIndex)] < UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + writeByte((byte) (majorType | val[(short) (startOff + msbIndex)])); + } else if (diff == 1) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte(val[(short) (startOff + msbIndex)]); + } else if (diff == 2) { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeBytes(val, (short) (startOff + msbIndex), (short) 2); + } else if (diff <= 4) { + writeByte((byte) (majorType | UINT32_LENGTH)); + writeBytes(val, (short) (startOff + len - 4), (short) 4); + } else { + writeByte((byte) (majorType | UINT64_LENGTH)); + writeBytes(val, startOff, (short) 8); + } + } + + // find out the most significant byte + public short findMsb(byte[] buf, short offset, short len) { + byte index = 0; + // find out the most significant byte + while (index < len) { + if (buf[(short) (offset + index)] > 0) { + break; + } else if (buf[(short) (offset + index)] < 0) { + break; + } + index++; // index will be equal to len if value is 0. + } + return index; + } + + public void computeOnesCompliment(short msbIndex, byte[] buf, short offset, short len) { + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + short correctedOffset = offset; + short correctedLen = len; + // The offset and length of the buffer for Short and Byte types should be + // corrected before computing the 1s compliment. The reason for doing this + // is to avoid computation of 1s compliment on the MSB bytes. + if (diff == 0) { + // Fail + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else if (diff == 1) { + correctedOffset = (short) (offset + 3); + correctedLen = 1; + } else if (diff == 2) { + correctedOffset = (short) (offset + 2); + correctedLen = 2; + } + // For int and long values the len and offset values are always proper. + // int - 4 bytes + // long - 8 bytes. + KMUtils.computeOnesCompliment(buf, correctedOffset, correctedLen); + } + + // Encoding rule for negative Integers is taken from + // https://datatracker.ietf.org/doc/html/rfc7049#section-2.1, Major type 1. + public short handleNegIntegerEncodingRule(byte[] buf, short offset, short len) { + short msbIndex = findMsb(buf, offset, len); + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(msbIndex, buf, offset, len); + return msbIndex; + } + + // Note: This function modifies the buffer's actual value. So after encoding, restore the original + // value by calling removeNegIntegerEncodingRule(). + public short applyNegIntegerEncodingRule(byte[] buf, short offset, short len) { + return handleNegIntegerEncodingRule(buf, offset, len); + } + + public void removeNegIntegerEncodingRule( + byte[] buf, short offset, short len, short origMsbIndex) { + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(origMsbIndex, buf, offset, len); + } + + private void encodeNegInteger(short obj) { + byte[] val = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short startOff = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(val, startOff, len); + encodeInteger(val, len, startOff, NEG_INT_TYPE); + removeNegIntegerEncodingRule(val, startOff, len, msbIndex); + } + + private void encodeSemanticTag(short obj) { + short tag = KMSemanticTag.cast(obj).getKeyPtr(); + encode(KMSemanticTag.cast(obj).getValuePtr()); + encodeInteger( + KMInteger.cast(tag).getBuffer(), + KMInteger.cast(tag).length(), + KMInteger.cast(tag).getStartOff(), + SEMANTIC_TAG_TYPE); + } + + private void encodeUnsignedInteger(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + encodeInteger(val, len, startOff, UINT_TYPE); + } + + private void encodeSimpleValue(short obj) { + byte value = KMSimpleValue.cast(obj).getValue(); + writeByte((byte) (SIMPLE_VALUE_TYPE | value)); + } + + private void encodeTextString(short obj) { + writeMajorTypeWithLength(TSTR_TYPE, KMTextString.cast(obj).length()); + writeBytes( + KMTextString.cast(obj).getBuffer(), + KMTextString.cast(obj).getStartOff(), + KMTextString.cast(obj).length()); + } + + public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(BYTES_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + private void encodeByteBlob(short obj) { + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.cast(obj).length()); + writeBytes( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + + public short getEncodedLength(short ptr) { + short len = 0; + short type = KMType.getType(ptr); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + len += getEncodedByteBlobLength(ptr); + break; + case KMType.TEXT_STRING_TYPE: + len += getEncodedTextStringLength(ptr); + break; + case KMType.INTEGER_TYPE: + len += getEncodedIntegerLength(ptr); + break; + case KMType.NEG_INTEGER_TYPE: + len += getEncodedNegIntegerLength(ptr); + break; + case KMType.ARRAY_TYPE: + len += getEncodedArrayLen(ptr); + break; + case KMType.MAP_TYPE: + len += getEncodedMapLen(ptr); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(ptr); + len += getEncodedCosePairTagLen(cosePairTagType, ptr); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + len += getEncodedArrayLen(KMCoseMap.getVals(ptr)); + break; + default: + KMException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + private short getEncodedCosePairTagLen(short tagType, short exp) { + short length = 0; + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + length = getEncodedLength(cosePairByteBlobTag.getKeyPtr()); + length += getEncodedLength(cosePairByteBlobTag.getValuePtr()); + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + length = getEncodedLength(cosePairIntTag.getValuePtr()); + length += getEncodedLength(cosePairIntTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + length = getEncodedLength(cosePairNegIntegerTag.getValuePtr()); + length += getEncodedLength(cosePairNegIntegerTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + length = getEncodedLength(cosePairSimpleValueTag.getValuePtr()); + length += getEncodedLength(cosePairSimpleValueTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + length = getEncodedLength(cosePairTextStringTag.getValuePtr()); + length += getEncodedLength(cosePairTextStringTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + length = getEncodedLength(cosePairCoseKeyTag.getValuePtr()); + length += getEncodedLength(cosePairCoseKeyTag.getKeyPtr()); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return length; + } + + private short getEncodedMapLen(short obj) { + short mapLen = KMMap.cast(obj).length(); + short len = getEncodedBytesLength(mapLen); + short index = 0; + while (index < mapLen) { + len += getEncodedLength(KMMap.cast(obj).getKey(index)); + len += getEncodedLength(KMMap.cast(obj).getKeyValue(index)); + index++; + } + return len; + } + + private short getEncodedArrayLen(short obj) { + short arrLen = KMArray.cast(obj).length(); + short len = getEncodedBytesLength(arrLen); + short index = 0; + short subObj; + while (index < arrLen) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + len += getEncodedLength(subObj); + } + index++; + } + return len; + } + + public short getEncodedBytesLength(short len) { + short ret = 0; + if (len < KMEncoder.UINT8_LENGTH && len >= 0) { + ret = 1; + } else if (len >= KMEncoder.UINT8_LENGTH && len <= (short) 0x00FF) { + ret = 2; + } else if (len > (short) 0x00FF && len <= (short) 0x7FFF) { + ret = 3; + } else { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return ret; + } + + private short getEncodedByteBlobLength(short obj) { + short len = KMByteBlob.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedTextStringLength(short obj) { + short len = KMTextString.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedNegIntegerLength(short obj) { + byte[] buf = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short offset = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(buf, offset, len); + short ret = getEncodedIntegerLength(buf, offset, len); + removeNegIntegerEncodingRule(buf, offset, len, msbIndex); + return ret; + } + + private short getEncodedIntegerLength(byte[] val, short startOff, short len) { + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + switch (diff) { + case 0: + case 1: // Byte + if ((val[(short) (startOff + msbIndex)] < KMEncoder.UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + return (short) 1; + } else { + return (short) 2; + } + case 2: // Short + return (short) 3; + case 3: + case 4: // UInt32 + return (short) 5; + case 5: + case 6: + case 7: + case 8: // UInt64 + return (short) 9; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return 0; + } + + private short getEncodedIntegerLength(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + return getEncodedIntegerLength(val, startOff, len); + } + + private void writeByteValue(byte val) { + if ((val < UINT8_LENGTH) && (val >= 0)) { + writeByte((byte) (UINT_TYPE | val)); + } else { + writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); + writeByte(val); + } + } + + private void writeTag(short tagType, short tagKey) { + writeByte((byte) (UINT_TYPE | UINT32_LENGTH)); + writeShort(tagType); + writeShort(tagKey); + } + + private void writeMajorTypeWithLength(byte majorType, short len) { + if (len <= TINY_PAYLOAD) { + writeByte((byte) (majorType | (byte) (len & ADDITIONAL_MASK))); + } else if (len < SHORT_PAYLOAD) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte((byte) (len & 0xFF)); + } else { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeShort(len); + } + } + + private void writeBytes(byte[] buf, short start, short len) { + byte[] buffer = (byte[]) bufferRef[0]; + Util.arrayCopyNonAtomic(buf, start, buffer, scratchBuf[START_OFFSET], len); + incrementStartOff(len); + } + + private void writeShort(short val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = (byte) ((val >> 8) & 0xFF); + incrementStartOff((short) 1); + buffer[scratchBuf[START_OFFSET]] = (byte) ((val & 0xFF)); + incrementStartOff((short) 1); + } + + private void writeByte(byte val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = val; + incrementStartOff((short) 1); + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] >= scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java new file mode 100644 index 0000000..44bf477 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -0,0 +1,166 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnum represents an enumeration specified in android keymaster hal specifications. It + * corresponds to uint CBOR type and it is a byte value. struct{byte ENUM_TYPE; short length; + * struct{short enumType; byte val}} + */ +public class KMEnum extends KMType { + + private static KMEnum prototype; + + // The allowed enum types. + private static short[] types = { + HARDWARE_TYPE, + KEY_FORMAT, + KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, + DEVICE_LOCKED, + USER_AUTH_TYPE, + PURPOSE, + ECCURVE, + RULE + }; + + private static Object[] enums = null; + + private KMEnum() {} + + private static KMEnum proto(short ptr) { + if (prototype == null) { + prototype = new KMEnum(); + } + KMType.instanceTable[KM_ENUM_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(ENUM_TYPE); + } + + public static KMEnum cast(short ptr) { + if (heap[ptr] != ENUM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short enumType) { + if (!validateEnum(enumType, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + return ptr; + } + + public static short instance(short enumType, byte val) { + if (!validateEnum(enumType, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 3); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + heap[(short) (ptr + TLV_HEADER_SIZE + 2)] = val; + return ptr; + } + + private static void create() { + // The allowed enum values to corresponding enum types in the types array. + if (enums == null) { + enums = + new Object[] { + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX}, + new byte[] {X509, PKCS8, RAW}, + new byte[] { + DERIVATION_NONE, + RFC5869_SHA256, + ISO18033_2_KDF1_SHA1, + ISO18033_2_KDF1_SHA256, + ISO18033_2_KDF2_SHA1, + ISO18033_2_KDF2_SHA256 + }, + new byte[] {SELF_SIGNED_BOOT, VERIFIED_BOOT, UNVERIFIED_BOOT, FAILED_BOOT}, + new byte[] {DEVICE_LOCKED_TRUE, DEVICE_LOCKED_FALSE}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH}, + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {P_224, P_256, P_384, P_521}, + new byte[] {IGNORE_INVALID_TAGS, FAIL_ON_INVALID_TAGS} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) types.length; + while (--index >= 0) { + if (types[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public short length() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + 1)); + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)]; + } + + public void setVal(byte val) { + heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)] = val; + } + + public short getEnumType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE)); + } + + public void setEnumType(short type) { + Util.setShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE), type); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java new file mode 100644 index 0000000..ea73c40 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -0,0 +1,305 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumArrayTag represents ENUM_REP tag type. It has following structure, struct{byte TAG_TYPE; + * short length; struct{short ENUM_ARRAY_TAG; short tagKey; sequence of byte values}} + */ +public class KMEnumArrayTag extends KMTag { + + private static KMEnumArrayTag prototype; + + // The allowed tag keys of enum array type. + private static short[] tags = {PURPOSE, BLOCK_MODE, DIGEST, PADDING, RSA_OAEP_MGF_DIGEST}; + + // Tag Values. + private static Object[] enums = null; + + private KMEnumArrayTag() {} + + private static KMEnumArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumArrayTag(); + } + KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key) { + byte[] vals = getAllowedEnumValues(key); + if (vals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short blobPtr = KMByteBlob.exp(); + return instance(key, blobPtr); + } + + public static short instance(short key, short byteBlob) { + byte[] allowedVals = getAllowedEnumValues(key); + if (allowedVals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + KMByteBlob blob = KMByteBlob.cast(byteBlob); + short byteIndex = 0; + short enumIndex; + boolean validValue; + while (byteIndex < blob.length()) { + enumIndex = 0; + validValue = false; + while (enumIndex < allowedVals.length) { + if (blob.get(byteIndex) == allowedVals[enumIndex]) { + validValue = true; + break; + } + enumIndex++; + } + if (!validValue) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byteIndex++; + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMEnumArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_ARRAY_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // allowed tag values. + enums = + new Object[] { + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {ECB, CBC, CTR, GCM}, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + new byte[] { + PADDING_NONE, RSA_OAEP, RSA_PSS, RSA_PKCS1_1_5_ENCRYPT, RSA_PKCS1_1_5_SIGN, PKCS7 + }, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + }; + } + } + + private static byte[] getAllowedEnumValues(short key) { + create(); + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return (byte[]) enums[index]; + } + } + return null; + } + + public static short getValues(short tagId, short params, byte[] buf, short start) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag == KMType.INVALID_VALUE) { + return KMType.INVALID_VALUE; + } + tag = KMEnumArrayTag.cast(tag).getValues(); + return KMByteBlob.cast(tag).getValues(buf, start); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + while (index < KMEnumArrayTag.cast(tag).length()) { + if (tagValue == KMEnumArrayTag.cast(tag).get(index)) { + return true; + } + index++; + } + } + return false; + } + + public static short length(short tagId, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + return KMEnumArrayTag.cast(tag).length(); + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_ARRAY_TAG; + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } + + public short get(short index) { + return KMByteBlob.cast(getValues()).get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (get(index) == (byte) tagValue) { + return true; + } + index++; + } + return false; + } + + public boolean isValidDigests(byte alg) { + short index = 0; + short digest; + while (index < length()) { + digest = get(index); + switch (alg) { + case KMType.EC: + case KMType.RSA: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256 && digest != KMType.SHA1) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (digest != KMType.DIGEST_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPaddingModes(byte alg) { + short index = 0; + short padding; + while (index < length()) { + padding = get(index); + switch (alg) { + case KMType.RSA: + if (padding != KMType.RSA_OAEP + && padding != KMType.PADDING_NONE + && padding != KMType.RSA_PKCS1_1_5_SIGN + && padding != KMType.RSA_PKCS1_1_5_ENCRYPT + && padding != KMType.RSA_PSS) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (padding != KMType.PKCS7 && padding != KMType.PADDING_NONE) { + return false; + } + break; + case KMType.EC: + case KMType.HMAC: + if (padding != PADDING_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPurpose(byte alg) { + short index = 0; + short purpose; + while (index < length()) { + purpose = get(index); + switch (purpose) { + case KMType.DECRYPT: + case KMType.ENCRYPT: + if (alg != KMType.RSA && alg != KMType.AES && alg != KMType.DES) { + return false; + } + break; + case KMType.SIGN: + case KMType.VERIFY: + if (alg != KMType.HMAC && alg != KMType.RSA && alg != KMType.EC) { + return false; + } + break; + case KMType.WRAP_KEY: + if (alg != KMType.RSA) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidBlockMode(byte alg) { + if (alg == KMType.AES || alg == KMType.DES) { + return true; + } else { + return false; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java new file mode 100644 index 0000000..a7bcbe6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -0,0 +1,152 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumTag represents ENUM Tag type specified in android keymaster hal specifications. struct{byte + * TAG_TYPE; short length; struct{short ENUM_TAG; short tagKey; byte value}} + */ +public class KMEnumTag extends KMTag { + + private static KMEnumTag prototype; + + // The allowed tag keys of type enum tag. + private static short[] tags = { + ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE + }; + + private static Object[] enums = null; + + private KMEnumTag() {} + + private static KMEnumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumTag(); + } + KMType.instanceTable[KM_ENUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateEnum(key, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short) 4); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + return ptr; + } + + public static short instance(short key, byte val) { + if (!validateEnum(key, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = val; + return ptr; + } + + public static KMEnumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // enum tag values. + enums = + new Object[] { + new byte[] {RSA, DES, EC, AES, HMAC}, + new byte[] {P_224, P_256, P_384, P_521, CURVE_25519}, + new byte[] {STANDALONE, REQUIRES_FILE_SYSTEM}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH, ANY}, + new byte[] {GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public static short getValue(short tagKey, short keyParameters) { + short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagKey, keyParameters); + if (tagPtr != KMType.INVALID_VALUE) { + return heap[(short) (tagPtr + TLV_HEADER_SIZE + 4)]; + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_TAG; + } + + public byte getValue() { + return heap[(short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java new file mode 100644 index 0000000..bbae870 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java @@ -0,0 +1,134 @@ +/* + * 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; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short INCOMPATIBLE_PURPOSE = 3; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INCOMPATIBLE_ALGORITHM = 5; + public static final short UNSUPPORTED_KEY_SIZE = 6; + public static final short UNSUPPORTED_BLOCK_MODE = 7; + public static final short INCOMPATIBLE_BLOCK_MODE = 8; + public static final short UNSUPPORTED_MAC_LENGTH = 9; + public static final short UNSUPPORTED_PADDING_MODE = 10; + public static final short INCOMPATIBLE_PADDING_MODE = 11; + public static final short UNSUPPORTED_DIGEST = 12; + public static final short INCOMPATIBLE_DIGEST = 13; + + public static final short UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = 19; + + /** For PKCS8 & PKCS12 */ + public static final short INVALID_INPUT_LENGTH = 21; + + public static final short KEY_USER_NOT_AUTHENTICATED = 26; + public static final short INVALID_OPERATION_HANDLE = 28; + public static final short INSUFFICIENT_BUFFER_SPACE = 29; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_KEY_BLOB = 33; + + public static final short INVALID_ARGUMENT = 38; + public static final short UNSUPPORTED_TAG = 39; + public static final short INVALID_TAG = 40; + public static final short IMPORT_PARAMETER_MISMATCH = 44; + public static final short OPERATION_CANCELLED = 46; + + public static final short MISSING_NONCE = 51; + public static final short INVALID_NONCE = 52; + public static final short MISSING_MAC_LENGTH = 53; + public static final short CALLER_NONCE_PROHIBITED = 55; + public static final short KEY_MAX_OPS_EXCEEDED = 56; + public static final short INVALID_MAC_LENGTH = 57; + public static final short MISSING_MIN_MAC_LENGTH = 58; + public static final short UNSUPPORTED_MIN_MAC_LENGTH = 59; + public static final short UNSUPPORTED_EC_CURVE = 61; + public static final short KEY_REQUIRES_UPGRADE = 62; + + public static final short ATTESTATION_CHALLENGE_MISSING = 63; + public static final short ATTESTATION_APPLICATION_ID_MISSING = 65; + public static final short CANNOT_ATTEST_IDS = 66; + public static final short ROLLBACK_RESISTANCE_UNAVAILABLE = 67; + + public static final short NO_USER_CONFIRMATION = 71; + public static final short DEVICE_LOCKED = 72; + public static final short EARLY_BOOT_ENDED = 73; + public static final short ATTESTATION_KEYS_NOT_PROVISIONED = 74; + public static final short INCOMPATIBLE_MGF_DIGEST = 78; + public static final short UNSUPPORTED_MGF_DIGEST = 79; + public static final short MISSING_NOT_BEFORE = 80; + public static final short MISSING_NOT_AFTER = 81; + public static final short MISSING_ISSUER_SUBJECT_NAME = 82; + public static final short INVALID_ISSUER_SUBJECT_NAME = 83; + + public static final short UNIMPLEMENTED = 100; + public static final short UNKNOWN_ERROR = 1000; + + // Extended errors + public static final short SW_CONDITIONS_NOT_SATISFIED = 10001; + public static final short UNSUPPORTED_CLA = 10002; + public static final short INVALID_P1P2 = 10003; + public static final short UNSUPPORTED_INSTRUCTION = 10004; + public static final short CMD_NOT_ALLOWED = 10005; + public static final short SW_WRONG_LENGTH = 10006; + public static final short INVALID_DATA = 10007; + + // Crypto errors + public static final short CRYPTO_ILLEGAL_USE = 10008; + public static final short CRYPTO_ILLEGAL_VALUE = 10009; + public static final short CRYPTO_INVALID_INIT = 10010; + public static final short CRYPTO_NO_SUCH_ALGORITHM = 10011; + public static final short CRYPTO_UNINITIALIZED_KEY = 10012; + // Generic Unknown error. + public static final short GENERIC_UNKNOWN_ERROR = 10013; + + // Remote key provisioning error codes. + public static final short STATUS_FAILED = 32000; + public static final short STATUS_INVALID_MAC = 32001; + public static final short STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 32002; + public static final short STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 32003; + public static final short STATUS_INVALID_EEK = 32004; + public static final short INVALID_STATE = 32005; + + public static short translate(short err) { + switch (err) { + case SW_CONDITIONS_NOT_SATISFIED: + case UNSUPPORTED_CLA: + case INVALID_P1P2: + case INVALID_DATA: + case CRYPTO_ILLEGAL_USE: + case CRYPTO_ILLEGAL_VALUE: + case CRYPTO_INVALID_INIT: + case CRYPTO_UNINITIALIZED_KEY: + case GENERIC_UNKNOWN_ERROR: + case CMD_NOT_ALLOWED: + case UNKNOWN_ERROR: + return UNKNOWN_ERROR; + case CRYPTO_NO_SUCH_ALGORITHM: + return UNSUPPORTED_ALGORITHM; + case UNSUPPORTED_INSTRUCTION: + case SW_WRONG_LENGTH: + return UNIMPLEMENTED; + } + return err; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java new file mode 100644 index 0000000..e6b1d37 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -0,0 +1,171 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHardwareAuthToken represents HardwareAuthToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HW_AUTH_TOKEN_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMInteger Challenge; KMInteger UserId; KMInteger AuthenticatorId; UserAuthType + * HwAuthenticatorId; KMInteger TimeStamp; KMByteBlob Mac} + */ +public class KMHardwareAuthToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte USER_ID = 0x01; + public static final byte AUTHENTICATOR_ID = 0x02; + public static final byte HW_AUTHENTICATOR_TYPE = 0x03; + public static final byte TIMESTAMP = 0x04; + public static final byte MAC = 0x05; + + private static KMHardwareAuthToken prototype; + + private KMHardwareAuthToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(USER_ID, KMInteger.exp()); + arr.add(AUTHENTICATOR_ID, KMInteger.exp()); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHardwareAuthToken proto(short ptr) { + if (prototype == null) { + prototype = new KMHardwareAuthToken(); + } + KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(USER_ID, KMInteger.uint_16((short) 0)); + arr.add(AUTHENTICATOR_ID, KMInteger.uint_16((short) 0)); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 6) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHardwareAuthToken cast(short ptr) { + if (heap[ptr] != HW_AUTH_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getUserId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(USER_ID); + } + + public void setUserId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(USER_ID, vals); + } + + public short getAuthenticatorId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); + } + + public void setAuthenticatorId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); + } + + public short getHwAuthenticatorType() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); + } + + public void setHwAuthenticatorType(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java new file mode 100644 index 0000000..8642803 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -0,0 +1,110 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHmacSharingParameters represents HmacSharingParameters structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HMAC_SHARING_PARAM_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMByteBlob Seed; KMByteBlob Nonce} + */ +public class KMHmacSharingParameters extends KMType { + + public static final byte SEED = 0x00; + public static final byte NONCE = 0x01; + + private static KMHmacSharingParameters prototype; + + private KMHmacSharingParameters() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SEED, KMByteBlob.exp()); + arr.add(NONCE, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHmacSharingParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMHmacSharingParameters(); + } + KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short) 2); + if (KMArray.cast(vals).length() != 2) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHmacSharingParameters cast(short ptr) { + if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getNonce() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(NONCE); + } + + public void setNonce(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(NONCE, vals); + } + + public short getSeed() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SEED); + } + + public void setSeed(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SEED, vals); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java new file mode 100644 index 0000000..b09de0f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -0,0 +1,215 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8 bit, 16 bit, 32 bit and 64 bit unsigned integer. It corresponds to CBOR uint type. + * struct{byte INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMInteger extends KMType { + + public static final byte UINT_32 = 4; + public static final byte UINT_64 = 8; + private static KMInteger prototype; + + protected KMInteger() {} + + private static KMInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMInteger(); + } + KMType.instanceTable[KM_INTEGER_OFFSET] = ptr; + return prototype; + } + + // | TYPE(1) | LEN(2) | DATA(4 / 8) | + public static short exp() { + return KMType.exp(INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = UINT_64; + } else { + length = UINT_32; + } + return KMType.instance(INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + short ptr = instance(UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + short ptr = instance(UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + short ptr = instance(UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + short ptr = instance(UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64); + return ptr; + } + + public static short compare(short num1, short num2) { + short num1Buf = repository.alloc((short) 8); + short num2Buf = repository.alloc((short) 8); + Util.arrayFillNonAtomic(repository.getHeap(), num1Buf, (short) 8, (byte) 0); + Util.arrayFillNonAtomic(repository.getHeap(), num2Buf, (short) 8, (byte) 0); + short len = KMInteger.cast(num1).length(); + KMInteger.cast(num1).getValue(repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len); + len = KMInteger.cast(num2).length(); + KMInteger.cast(num2).getValue(repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len); + return KMInteger.unsignedByteArrayCompare( + repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, (short) 8); + } + + public static byte unsignedByteArrayCompare( + byte[] a1, short offset1, byte[] a2, short offset2, short length) { + byte count = (byte) 0; + short val1 = (short) 0; + short val2 = (short) 0; + + for (; count < length; count++) { + val1 = (short) (a1[(short) (count + offset1)] & 0x00FF); + val2 = (short) (a2[(short) (count + offset2)] & 0x00FF); + + if (val1 < val2) { + return -1; + } + if (val1 > val2) { + return 1; + } + } + return 0; + } + + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + // Get the start of value + public short getStartOff() { + return (short) (getBaseOffset() + TLV_HEADER_SIZE); + } + + public void getValue(byte[] dest, short destOff, short length) { + if (length < length()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (length > length()) { + length = length(); + destOff += length; + } + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length); + } + + public void setValue(byte[] src, short srcOff) { + Util.arrayCopyNonAtomic(src, srcOff, heap, getStartOff(), length()); + } + + public short value(byte[] dest, short destOff) { + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length()); + return length(); + } + + public short toLittleEndian(byte[] dest, short destOff) { + short index = (short) (length() - 1); + while (index >= 0) { + dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)]; + index--; + } + return length(); + } + + public short getShort() { + return Util.getShort(heap, (short) (getStartOff() + 2)); + } + + public short getSignificantShort() { + return Util.getShort(heap, getStartOff()); + } + + public byte getByte() { + return heap[(short) (getStartOff() + 3)]; + } + + public boolean isZero() { + if (getShort() == 0 && getSignificantShort() == 0) { + return true; + } + return false; + } + + protected short getBaseOffset() { + return instanceTable[KM_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java new file mode 100644 index 0000000..bf45981 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -0,0 +1,163 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerArrayTag represents UINT_REP and ULONG_REP tags specified in keymaster hal specs. + * struct{byte TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG; short tagKey; short arrPtr}, + * where arrPtr is the pointer to KMArray of KMInteger instances. + */ +public class KMIntegerArrayTag extends KMTag { + + private static final short[] tags = {USER_SECURE_ID}; + private static KMIntegerArrayTag prototype; + + private KMIntegerArrayTag() {} + + private static KMIntegerArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerArrayTag(); + } + KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(KMInteger.exp()); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(); + return instance(tagType, key, arrPtr); + } + + public static short instance(short tagType, short key, short arrObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[arrObj] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrObj); + return ptr; + } + + public static KMIntegerArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + tag = KMIntegerArrayTag.cast(tag).getValues(); + while (index < KMArray.cast(tag).length()) { + if (KMInteger.compare(tagValue, KMArray.cast(tag).get(index)) == 0) { + return true; + } + index++; + } + } + return false; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short ptr = getValues(); + return KMArray.cast(ptr).length(); + } + + public void add(short index, short val) { + KMArray arr = KMArray.cast(getValues()); + arr.add(index, val); + } + + public short get(short index) { + KMArray arr = KMArray.cast(getValues()); + return arr.get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (KMInteger.compare(tagValue, get(index)) == 0) { + return true; + } + index++; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java new file mode 100644 index 0000000..d4c4458 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -0,0 +1,218 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerTag represents UINT, ULONG and DATE tags specified in keymaster hal specs. struct{byte + * TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG/DATE_TAG; short tagKey; 4 or 8 byte + * value}} + */ +public class KMIntegerTag extends KMTag { + + // Allowed tag keys. + private static final short[] tags = { + // UINT + KEYSIZE, + MIN_MAC_LENGTH, + MIN_SEC_BETWEEN_OPS, + MAX_USES_PER_BOOT, + USERID, + AUTH_TIMEOUT, + OS_VERSION, + OS_PATCH_LEVEL, + VENDOR_PATCH_LEVEL, + BOOT_PATCH_LEVEL, + MAC_LENGTH, + // ULONG + RSA_PUBLIC_EXPONENT, + // DATE + ACTIVE_DATETIME, + ORIGINATION_EXPIRE_DATETIME, + USAGE_EXPIRE_DATETIME, + CREATION_DATETIME, + CERTIFICATE_NOT_BEFORE, + CERTIFICATE_NOT_AFTER, + USAGE_COUNT_LIMIT, + // custom tag + AUTH_TIMEOUT_MILLIS, + }; + private static KMIntegerTag prototype; + + private KMIntegerTag() {} + + private static KMIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerTag(); + } + KMType.instanceTable[KM_INTEGER_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + return instance(tagType, key, intPtr); + } + + public static short instance(short tagType, short key, short intObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[intObj] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intObj); + return ptr; + } + + public static KMIntegerTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == DATE_TAG) || (tagType == UINT_TAG) || (tagType == ULONG_TAG); + } + + public static short getShortValue(short tagType, short tagKey, short keyParameters) { + short ptr; + if (tagType == UINT_TAG) { + ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + if (KMInteger.cast(ptr).getSignificantShort() == 0) { + return KMInteger.cast(ptr).getShort(); + } + } + } + return KMType.INVALID_VALUE; + } + + public static short getValue( + byte[] buf, short offset, short tagType, short tagKey, short keyParameters) { + short ptr; + if ((tagType == UINT_TAG) || (tagType == ULONG_TAG) || (tagType == DATE_TAG)) { + ptr = KMKeyParameters.findTag(tagType, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + return KMInteger.cast(ptr).value(buf, offset); + } + } + return KMType.INVALID_VALUE; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + KMInteger obj = KMInteger.cast(getValue()); + return obj.length(); + } + + public boolean isValidKeySize(byte alg) { + short val = KMIntegerTag.cast(KMType.instanceTable[KM_INTEGER_TAG_OFFSET]).getValue(); + if (KMInteger.cast(val).getSignificantShort() != 0) { + return false; + } + val = KMInteger.cast(val).getShort(); + switch (alg) { + case KMType.RSA: + if (val == 2048) { + return true; + } + break; + case KMType.AES: + if (val == 128 || val == 256) { + return true; + } + break; + case KMType.DES: + if (val == 168) { + return true; + } + break; + case KMType.EC: + if (val == 256) { + return true; + } + break; + case KMType.HMAC: + if (val % 8 == 0 && val >= 64 && val <= 512) { + return true; + } + break; + default: + break; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java new file mode 100644 index 0000000..37b8b7f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -0,0 +1,124 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyCharacteristics represents KeyCharacteristics structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte KEY_CHAR_TYPE; short length=3; + * short arrayPtr} where arrayPtr is a pointer to ordered array with 1 or 3 following elements: + * {KMKeyParameters sb; KMKeyParameters tee; KMKeyParameters keystore} + */ +public class KMKeyCharacteristics extends KMType { + + public static final byte STRONGBOX_ENFORCED = 0x00; + public static final byte TEE_ENFORCED = 0x01; + public static final byte KEYSTORE_ENFORCED = 0x02; + private static KMKeyCharacteristics prototype; + + private KMKeyCharacteristics() {} + + public static short exp() { + short keyParamExp = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short) 3); + + KMArray arr = KMArray.cast(arrPtr); + arr.add(STRONGBOX_ENFORCED, keyParamExp); + arr.add(TEE_ENFORCED, keyParamExp); + arr.add(KEYSTORE_ENFORCED, keyParamExp); + return instance(arrPtr); + } + + private static KMKeyCharacteristics proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyCharacteristics(); + } + KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_CHAR_TYPE, (short) 3); + if (KMArray.cast(vals).length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyCharacteristics cast(short ptr) { + if (heap[ptr] != KEY_CHAR_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getKeystoreEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(KEYSTORE_ENFORCED); + } + + public void setKeystoreEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(KEYSTORE_ENFORCED, ptr); + } + + public short getTeeEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TEE_ENFORCED); + } + + public void setTeeEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TEE_ENFORCED, ptr); + } + + public short getStrongboxEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(STRONGBOX_ENFORCED); + } + + public void setStrongboxEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(STRONGBOX_ENFORCED, ptr); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java new file mode 100644 index 0000000..54ab6ee --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -0,0 +1,472 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyParameters represents KeyParameters structure from android keymaster hal specifications. It + * corresponds to CBOR map type. struct{byte KEY_PARAM_TYPE; short length=2; short arrayPtr} where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMKeyParameters extends KMType { + + private static final short[] customTags = { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + private static final short[] tagArr = { + // Unsupported tags. + KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS + }; + private static final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + }; + private static final short[] swEnforcedTagsArr = { + KMType.DATE_TAG, KMType.ACTIVE_DATETIME, + KMType.DATE_TAG, KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, + KMType.UINT_TAG, KMType.USERID, + KMType.DATE_TAG, KMType.CREATION_DATETIME, + KMType.UINT_TAG, KMType.USAGE_COUNT_LIMIT, + KMType.BOOL_TAG, KMType.ALLOW_WHILE_ON_BODY, + KMType.UINT_TAG, KMType.MAX_BOOT_LEVEL, + }; + private static final short[] teeEnforcedTagsArr = { + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + }; + private static final short[] invalidTagsArr = { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + }; + private static KMKeyParameters prototype; + + private KMKeyParameters() {} + + private static KMKeyParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyParameters(); + } + KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.FAIL_ON_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short expAny() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.IGNORE_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_PARAM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyParameters cast(short ptr) { + if (heap[ptr] != KEY_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short findTag(short tagType, short tagKey, short keyParam) { + KMKeyParameters instParam = KMKeyParameters.cast(keyParam); + return instParam.findTag(tagType, tagKey); + } + + public static boolean hasUnsupportedTags(short keyParamsPtr) { + byte index = 0; + short tagInd; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + while (tagInd < (short) tagArr.length) { + if ((tagArr[tagInd] == tagType) && (tagArr[(short) (tagInd + 1)] == tagKey)) { + return true; + } + tagInd += 2; + } + index++; + } + return false; + } + + // KDF, ECIES_SINGLE_HASH_MODE missing from types.hal + public static short makeSbEnforced( + short keyParamsPtr, + byte origin, + short osVersionObjPtr, + short osPatchObjPtr, + short vendorPatchObjPtr, + short bootPatchObjPtr, + byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) hwEnforcedTagArr.length) { + if ((hwEnforcedTagArr[tagInd] == tagType) + && (hwEnforcedTagArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + short originTag = KMEnumTag.instance(KMType.ORIGIN, origin); + Util.setShort(scratchPad, arrInd, originTag); + arrInd += 2; + short osVersionTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION, osVersionObjPtr); + Util.setShort(scratchPad, arrInd, osVersionTag); + arrInd += 2; + short osPatchTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, osPatchObjPtr); + Util.setShort(scratchPad, arrInd, osPatchTag); + arrInd += 2; + short vendorPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, vendorPatchObjPtr); + Util.setShort(scratchPad, arrInd, vendorPatchTag); + arrInd += 2; + short bootPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, bootPatchObjPtr); + Util.setShort(scratchPad, arrInd, bootPatchTag); + arrInd += 2; + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHwEnforced(short sb, short tee) { + short len = KMKeyParameters.cast(sb).length(); + len += KMKeyParameters.cast(tee).length(); + short hwEnf = KMArray.instance(len); + sb = KMKeyParameters.cast(sb).getVals(); + tee = KMKeyParameters.cast(tee).getVals(); + len = KMArray.cast(sb).length(); + short src = 0; + short dest = 0; + short val = 0; + while (src < len) { + val = KMArray.cast(sb).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + src = 0; + len = KMArray.cast(tee).length(); + while (src < len) { + val = KMArray.cast(tee).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + return KMKeyParameters.instance(hwEnf); + } + + // ALL_USERS, EXPORTABLE missing from types.hal + public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) swEnforcedTagsArr.length) { + if ((swEnforcedTagsArr[tagInd] == tagType) + && (swEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) teeEnforcedTagsArr.length) { + if ((teeEnforcedTagsArr[tagInd] == tagType) + && (teeEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + appId = KMTag.INVALID_VALUE; + } + } + short appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + appData = KMTag.INVALID_VALUE; + } + } + return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); + } + + public static short makeHidden( + short appIdBlob, short appDataBlob, short rootOfTrustBlob, byte[] scratchPad) { + // Order in which the hidden array is created should not change. + short index = 0; + KMByteBlob.cast(rootOfTrustBlob); + Util.setShort(scratchPad, index, rootOfTrustBlob); + index += 2; + if (appIdBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appIdBlob); + Util.setShort(scratchPad, index, appIdBlob); + index += 2; + } + if (appDataBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appDataBlob); + Util.setShort(scratchPad, index, appDataBlob); + index += 2; + } + return createKeyParameters(scratchPad, (short) (index / 2)); + } + + public static boolean isValidTag(short tagType, short tagKey) { + short index = 0; + if (tagKey == KMType.INVALID_TAG) { + return false; + } + while (index < invalidTagsArr.length) { + if ((tagType == invalidTagsArr[index]) && (tagKey == invalidTagsArr[(short) (index + 1)])) { + return false; + } + index += 2; + } + return true; + } + + public static short createKeyParameters(byte[] ptrArr, short len) { + short arrPtr = KMArray.instance(len); + short index = 0; + short ptr = 0; + while (index < len) { + KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + index++; + ptr += 2; + } + return KMKeyParameters.instance(arrPtr); + } + + public static short makeCustomTags(short keyParams, byte[] scratchPad) { + short index = 0; + short tagPtr; + short offset = 0; + short len = (short) customTags.length; + short tagType; + while (index < len) { + tagType = customTags[(short) (index + 1)]; + switch (tagType) { + case KMType.AUTH_TIMEOUT_MILLIS: + short authTimeOutTag = + KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + if (authTimeOutTag != KMType.INVALID_VALUE) { + tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); + Util.setShort(scratchPad, offset, tagPtr); + offset += 2; + } + break; + default: + break; + } + index += 2; + } + return createKeyParameters(scratchPad, (short) (offset / 2)); + } + + public static short createAuthTimeOutMillisTag( + short authTimeOutTag, byte[] scratchPad, short offset) { + short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (offset + 8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, offset, (short) (offset + 8), (short) (offset + 16)); + return KMIntegerTag.instance( + KMType.ULONG_TAG, + KMType.AUTH_TIMEOUT_MILLIS, + KMInteger.uint_64(scratchPad, (short) (offset + 8))); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short findTag(short tagType, short tagKey) { + KMArray vals = KMArray.cast(getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short ret = KMType.INVALID_VALUE; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + if ((tagKey == key) && (tagType == type)) { + ret = obj; + break; + } + index++; + } + return ret; + } + + public void deleteCustomTags() { + short arrPtr = getVals(); + short index = (short) (customTags.length - 1); + short obj; + while (index >= 0) { + obj = findTag(customTags[(short) (index - 1)], customTags[index]); + if (obj != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).deleteLastEntry(); + } + index -= 2; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java new file mode 100644 index 0000000..b616085 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -0,0 +1,5030 @@ +/* + * 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.KMDeviceUniqueKeyPair; +import com.android.javacard.seprovider.KMException; +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 repository 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. + public static final byte[] F4 = {0x01, 0x00, 0x01}; + public static final byte AES_BLOCK_SIZE = 16; + public static final byte DES_BLOCK_SIZE = 8; + public static final short MASTER_KEY_SIZE = 128; + public static final byte WRAPPING_KEY_SIZE = 32; + public static final byte MAX_OPERATIONS_COUNT = 4; + public static final byte VERIFIED_BOOT_KEY_SIZE = 32; + public static final byte VERIFIED_BOOT_HASH_SIZE = 32; + public static final byte BOOT_PATCH_LVL_SIZE = 4; + 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 + }; + // "KeymasterSharedMac" + public static final byte[] ckdfLable = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, + 0x61, 0x63 + }; + // "Auth Verification" + public static final byte[] authVerification = { + 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, + 0x6E + }; + // "confirmation token" + public static final byte[] confirmationToken = { + 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B, + 0x65, 0x6E + }; + public static final short MAX_COSE_BUF_SIZE = (short) 1024; + // Maximum allowed buffer size for to encode the key parameters + // which is used while creating mac for key paramters. + public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K + // Data Dictionary items + public static final byte DATA_ARRAY_SIZE = 40; + public static final byte TMP_VARIABLE_ARRAY_SIZE = 5; + 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; + // 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 constants + public static final byte AES_GCM_AUTH_TAG_LENGTH = 16; + 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 short KEYBLOB_CURRENT_VERSION = 3; + // KeyBlob Verion 1 constant. + public static final short KEYBLOB_VERSION_1 = 1; + // KeyBlob array size constants. + public static final byte SYM_KEY_BLOB_SIZE_V2_V3 = 6; + public static final byte ASYM_KEY_BLOB_SIZE_V2_V3 = 7; + public static final byte SYM_KEY_BLOB_SIZE_V1 = 5; + public static final byte ASYM_KEY_BLOB_SIZE_V1 = 6; + public static final byte SYM_KEY_BLOB_SIZE_V0 = 4; + public static final byte ASYM_KEY_BLOB_SIZE_V0 = 5; + // Key type constants + public static final byte SYM_KEY_TYPE = 0; + 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 short MIN_HMAC_LENGTH_BITS = 64; + // 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_ADDITIONAL_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; + protected static final short KM_HAL_VERSION = (short) 0x5000; + // OEM lock / unlock verification constants. + 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 + }; + protected static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + // AddRngEntropy + protected static final short MAX_SEED_SIZE = 2048; + protected static final short MAX_CERT_SIZE = 3000; + protected static final short MAX_KEY_CHARS_SIZE = 512; + protected static final short MAX_KEYBLOB_SIZE = 1024; + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + private static final short DERIVE_KEY_INPUT_SIZE = (short) 256; + // 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 + }; + private static final byte[] dec319999Ms = { + (byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18 + }; + private static final byte[] dec319999 = { + 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, + }; + private static final byte[] jan01970 = { + 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + }; + 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, + }; + private static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + private static final short[] attTags = { + KMType.ATTESTATION_ID_BRAND, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_SERIAL + }; + private static final byte OEM_LOCK = 1; + private static final byte OEM_UNLOCK = 0; + // 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 + 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; + // ComputeHMAC constants + private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; + protected static RemotelyProvisionedComponentDevice rkp; + protected static KMEncoder encoder; + protected static KMDecoder decoder; + protected static KMRepository repository; + protected static KMSEProvider seProvider; + protected static KMOperationState[] opTable; + protected static KMKeymintDataStore kmDataStore; + + protected static short[] tmpVariables; + protected static short[] data; + protected static byte[] wrappingKey; + + /** 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(); + opTable = new KMOperationState[MAX_OPERATIONS_COUNT]; + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index] = new KMOperationState(); + index++; + } + KMType.initialize(); + if (!isUpgrading) { + kmDataStore.createMasterKey(MASTER_KEY_SIZE); + } + // initialize default values + initHmacNonceAndSeed(); + rkp = + new RemotelyProvisionedComponentDevice( + 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); + } + // 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(); + // TODO add logic to handle the extended length buffer. In this case the memory can be reused + // from extended buffer. + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); + repository.reclaimMemory(bufferLength); + return req; + } + + private static short createKeyBlobInstance(byte keyType) { + short arrayLen = 0; + switch (keyType) { + case ASYM_KEY_TYPE: + arrayLen = ASYM_KEY_BLOB_SIZE_V2_V3; + break; + case SYM_KEY_TYPE: + arrayLen = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return KMArray.instance(arrayLen); + } + + private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { + short index = 0; + short arr = KMKeyParameters.cast(params).getVals(); + short len = KMArray.cast(arr).length(); + short tag; + while (index < len) { + tag = KMArray.cast(arr).get(index); + cert.extensionTag(tag, hwEnforced); + index++; + } + } + + private static void setUniqueId(KMAttestationCert cert, short attAppId, byte[] scratchPad) { + if (!KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)) { + return; + } + // temporal count T + short time = + KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[KEY_PARAMETERS]); + if (time == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + time = KMIntegerTag.cast(time).getValue(); + + // Reset After Rotation R - it will be part of HW Enforced key + // characteristics + byte resetAfterRotation = 0; + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION)) { + resetAfterRotation = 0x01; + } + + cert.makeUniqueId( + scratchPad, + (short) 0, + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + KMInteger.cast(time).length(), + KMByteBlob.cast(attAppId).getBuffer(), + KMByteBlob.cast(attAppId).getStartOff(), + KMByteBlob.cast(attAppId).length(), + resetAfterRotation, + kmDataStore.getMasterKey()); + } + + private static void validateRSAKey(byte[] scratchPad) { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (!KMTag.isValidPublicExponent(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + // Generate key handlers + private static void generateRSAKey(byte[] scratchPad) { + // Validate RSA Key + validateRSAKey(scratchPad); + // Now generate 2048 bit RSA keypair for the given exponent + short[] lengths = tmpVariables; + data[PUB_KEY] = KMByteBlob.instance((short) 256); + data[SECRET] = KMByteBlob.instance((short) 256); + seProvider.createAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + lengths); + + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateAESKey() { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Block mode - array of byte values + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE)) { + short blockModes = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + // If it is a GCM mode + if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)) { + // Min mac length must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short macLength = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + macLength = KMIntegerTag.cast(macLength).getValue(); + // Validate the MIN_MAC_LENGTH for AES - should be multiple of 8, less then 128 bits + // and greater the 96 bits + if (KMInteger.cast(macLength).getSignificantShort() != 0 + || KMInteger.cast(macLength).getShort() > 128 + || KMInteger.cast(macLength).getShort() < 96 + || (KMInteger.cast(macLength).getShort() % 8) != 0) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + } + } + } + + private static void generateAESKey(byte[] scratchPad) { + validateAESKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short len = seProvider.createSymmetricKey(KMType.AES, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateECKeys() { + // Read key size + short ecCurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + /* In KeyMint 2.0, If EC_CURVE not provided, generateKey + * must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE. + */ + if (ecCurve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + short ecKeySize = KMEnumTag.getValue(KMType.KEYSIZE, data[KEY_PARAMETERS]); + if ((ecKeySize != KMType.INVALID_VALUE) && !KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateECKeys(byte[] scratchPad) { + validateECKeys(); + short[] lengths = tmpVariables; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateTDESKey() { + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + } + + private static void generateTDESKey(byte[] scratchPad) { + validateTDESKey(); + short len = seProvider.createSymmetricKey(KMType.DES, (short) 168, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateHmacKey() { + // If params does not contain any digest throw unsupported digest error. + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.UNSUPPORTED_DIGEST); + + // check whether digest sizes are greater then or equal to min mac length. + // Only SHA256 digest must be supported. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + // Read Minimum Mac length + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short minMacLength = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + + if (((short) (minMacLength % 8) != 0) + || minMacLength < MIN_HMAC_LENGTH_BITS + || minMacLength > SHA256_DIGEST_LEN_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + // Read Keysize + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateHmacKey(byte[] scratchPad) { + validateHmacKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + // generate HMAC Key + short len = seProvider.createSymmetricKey(KMType.HMAC, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + // This function is only called from processUpgradeKey command. + // 1. Update the latest values of OSVersion, OSPatch, VendorPatch and BootPatch in the + // KeyBlob's KeyCharacteristics. + // 2. Re-create KeyBlob's KeyCharacteristics from HW_PARAMS to make sure we don't miss + // anything which happens in these functions makeSbEnforced and makeTeeEnforced in + // the future. Like validations. + // 3. No need to create Keystore Enforced list here as it is not required to be included in + // the KeyBlob's KeyCharacteristics. + // 4. No need to create KeyCharacteristics as upgradeKey does not require to return any + // KeyCharacteristics back. + private static void upgradeKeyBlobKeyCharacteristics(short hwParams, byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + hwParams, (byte) data[ORIGIN], osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(hwParams, scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + } + + private static void makeKeyCharacteristics(byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + data[KEY_PARAMETERS], + (byte) data[ORIGIN], + osVersion, + osPatch, + vendorPatch, + bootPatch, + scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(data[KEY_PARAMETERS], scratchPad); + data[SW_PARAMETERS] = KMKeyParameters.makeKeystoreEnforced(data[KEY_PARAMETERS], scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setKeystoreEnforced(data[SW_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]); + } + + private static void createEncryptedKeyBlob(byte[] scratchPad) { + // make root of trust blob + data[ROT] = readROT(scratchPad, KEYBLOB_CURRENT_VERSION); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make hidden key params list + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad); + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_16(KEYBLOB_CURRENT_VERSION); + // create custom tags + data[CUSTOM_TAGS] = KMKeyParameters.makeCustomTags(data[HW_PARAMETERS], scratchPad); + // encrypt the secret and cryptographically attach that to authorization data + encryptSecret(scratchPad); + // create key blob array + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_NONCE, data[NONCE]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_VERSION_OFFSET, data[KEY_BLOB_VERSION_DATA_OFFSET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_CUSTOM_TAGS, data[CUSTOM_TAGS]); + + short tempChar = KMKeyCharacteristics.instance(); + short emptyParam = KMArray.instance((short) 0); + emptyParam = KMKeyParameters.instance(emptyParam); + KMKeyCharacteristics.cast(tempChar).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam); + KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar); + } + + // Read RoT + public static short readROT(byte[] scratchPad, short version) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + // As per IKeyMintDevice.aidl specification The root of trust + // consists of verifyBootKey, boot state and device locked. + if (version <= KEYBLOB_VERSION_1) { + // To parse old keyblobs verified boot hash is included in + // the root of trust. + len += kmDataStore.getVerifiedBootHash(scratchPad, (short) len); + } + short bootState = kmDataStore.getBootState(); + len = Util.setShort(scratchPad, len, bootState); + if (kmDataStore.isDeviceBootLocked()) { + scratchPad[len] = (byte) 1; + } else { + scratchPad[len] = (byte) 0; + } + len++; + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static void encryptSecret(byte[] scratchPad) { + // make nonce + data[NONCE] = KMByteBlob.instance(AES_GCM_NONCE_LENGTH); + data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); + seProvider.newRandomNumber( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length()); + // derive master key - stored in derivedKey + short len = deriveKey(scratchPad); + len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + null, + (short) 0, + (short) 0, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + + if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static byte getKeyType(short hardwareParams) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hardwareParams); + if (KMEnumTag.cast(alg).getValue() == KMType.RSA + || KMEnumTag.cast(alg).getValue() == KMType.EC) { + return ASYM_KEY_TYPE; + } + return SYM_KEY_TYPE; + } + + private static void makeAuthData(short version, byte[] scratchPad) { + // For KeyBlob V2: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + // For KeyBlob V1: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, VERSION and PUB_KEY. + // For KeyBlob V0: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS and PUB_KEY. + // VERSION is included only for KeyBlobs having version >= 1. + // PUB_KEY is included for only ASYMMETRIC KeyBlobs. + short index = 0; + short numParams = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + switch (version) { + case (short) 0: + numParams = 2; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 3; + Util.setShort(scratchPad, (short) 4, data[PUB_KEY]); + } + break; + case (short) 1: + numParams = 3; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 4; + Util.setShort(scratchPad, (short) 6, data[PUB_KEY]); + } + break; + case (short) 2: + numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + index = 0; + short len = 0; + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32), prevReclaimIndex); + Util.arrayCopyNonAtomic( + repository.getHeap(), + authIndex, + repository.getHeap(), + (short) (authIndex + len + 32), + (short) 32); + len = + seProvider.messageDigest256( + repository.getHeap(), + (short) (authIndex + 32), + (short) (len + 32), + repository.getHeap(), + authIndex); + if (len != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index++; + } + short authDataIndex = repository.alloc(len); + Util.arrayCopyNonAtomic( + repository.getHeap(), authIndex, repository.getHeap(), authDataIndex, len); + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + data[AUTH_DATA] = authDataIndex; + data[AUTH_DATA_LENGTH] = len; + } + + private static short deriveKeyForOldKeyBlobs(byte[] scratchPad) { + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + short len = + seProvider.hmacKDF( + kmDataStore.getMasterKey(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + scratchPad, + (short) 0); + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + private static short deriveKey(byte[] scratchPad) { + // For KeyBlob V3: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + short index = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. CUSTOM_TAGS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + short numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + short len = 0; + KMOperation operation = null; + try { + operation = + seProvider.initSymmetricOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) KMType.INVALID_VALUE, + (Object) kmDataStore.getMasterKey(), + KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, + (byte[]) null, + (short) 0, + (short) 0, + (short) 0, + false); + + byte arrayHeader = (byte) 0x80; + arrayHeader |= (byte) numParams; + ((byte[]) repository.getHeap())[authIndex] = arrayHeader; + operation.update(repository.getHeap(), authIndex, (short) 1); + + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) authIndex, prevReclaimIndex); + operation.update(repository.getHeap(), authIndex, len); + index++; + } + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + len = operation.sign(scratchPad, (short) 0, (short) 0, scratchPad, (short) 0); + } finally { + if (operation != null) { + operation.abort(); + } + } + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + public static void sendResponse(APDU apdu, short err) { + short resp = KMArray.instance((short) 1); + err = KMError.translate(err); + short error = KMInteger.uint_16(err); + KMArray.cast(resp).add((short) 0, error); + sendOutgoing(apdu, resp); + } + + public static void generateRkpKey(byte[] scratchPad, short keyParams) { + data[KEY_PARAMETERS] = keyParams; + generateECKeys(scratchPad); + // create key blob + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } + + public static short getPubKey() { + return data[PUB_KEY]; + } + + public static short getPivateKey() { + return data[KEY_BLOB]; + } + + /** + * Encodes the object to the provided apdu buffer. + * + * @param object Object to be encoded. + * @param apduBuf Buffer on which the encoded data is copied. + * @param apduOff Start offset of the buffer. + * @param maxLen Max value of the expected out length. + * @return length of the encoded buffer. + */ + public static short encodeToApduBuffer( + short object, byte[] apduBuf, short apduOff, short maxLen) { + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(maxLen); + short len = encoder.encode(object, repository.getHeap(), offset, prevReclaimIndex, maxLen); + Util.arrayCopyNonAtomic(repository.getHeap(), offset, apduBuf, apduOff, len); + // release memory + repository.reclaimMemory(maxLen); + return len; + } + + public static short validateCertChain( + boolean validateEekRoot, + byte expCertAlg, + byte expLeafCertAlg, + short certChainArr, + byte[] scratchPad, + Object[] authorizedEekRoots) { + short len = KMArray.cast(certChainArr).length(); + short coseHeadersExp = KMCoseHeaders.exp(); + // prepare exp for coseky + short coseKeyExp = KMCoseKey.exp(); + short ptr1; + short ptr2; + short signStructure; + short encodedLen; + short prevCoseKey = 0; + short keySize; + short alg = expCertAlg; + short index; + for (index = 0; index < len; index++) { + ptr1 = KMArray.cast(certChainArr).get(index); + + // validate protected Headers + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if (!KMCoseHeaders.cast(ptr2).isDataValid(rkp.rkpTmpVariables, alg, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // parse and get the public key from payload. + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if ((index == (short) (len - 1)) && len > 1) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2) + .isDataValid( + rkp.rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + alg, + KMType.INVALID_VALUE, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 + == Util.arrayCompare( + scratchPad, + (short) 0, + (byte[]) authorizedEekRoots[i], + (short) 0, + (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short signatureLen = + rkp.encodeES256CoseSignSignature( + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getStartOff(), + KMByteBlob.length(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + scratchPad, + (short) (keySize + encodedLen)); + + if (!seProvider.ecVerify256( + scratchPad, + (short) 0, + keySize, + scratchPad, + keySize, + encodedLen, + scratchPad, + (short) (keySize + encodedLen), + signatureLen)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } + + public static short generateBcc(boolean testMode, byte[] scratchPad) { + if (!testMode && kmDataStore.isProvisionLocked()) { + KMException.throwIt(KMError.STATUS_FAILED); + } + KMDeviceUniqueKeyPair deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(testMode); + 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_KEY_OP_VERIFY), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + scratchPad, + (short) 0, + temp, + KMType.INVALID_VALUE, + false); + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct payload. + short payload = + KMCose.constructCoseCertPayload( + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.ISSUER), + KMTextString.instance( + KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)), + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.SUBJECT), + KMTextString.instance( + KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0), + KMByteBlob.instance(scratchPad, (short) 0, temp)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0), + KMByteBlob.instance( + KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length))); + // temp temporarily holds the length of encoded cert payload. + temp = + KMKeymasterApplet.encodeToApduBuffer( + payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + payload = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // protected header + short protectedHeader = + KMCose.constructHeaders( + rkp.rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // temp temporarily holds the length of encoded headers. + temp = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // unprotected headers. + short arr = KMArray.instance((short) 0); + short unprotectedHeader = KMCoseHeaders.instance(arr); + + // construct cose sign structure. + short coseSignStructure = + KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // temp temporarily holds the length of encoded sign structure. + // Encode cose Sign_Structure. + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // do sign + short len = + seProvider.ecSign256(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 bcc = KMArray.instance((short) 2); + KMArray.cast(bcc).add((short) 0, coseKey); + KMArray.cast(bcc).add((short) 1, coseSign1); + return bcc; + } + + 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; + } + } + + /** + * 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_CHALLENGE_CMD: + case INS_UPDATE_EEK_CHAIN_CMD: + case INS_UPDATE_KEY_CMD: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_RESPONSE_CMD: + case INS_GET_RKP_HARDWARE_INFO: + rkp.process(apduIns, apdu); + break; + // KeyMint 2.0 + case INS_GET_ROT_CHALLENGE_CMD: + processGetRootOfTrustChallenge(apdu); + break; + case INS_GET_ROT_DATA_CMD: + sendResponse(apdu, KMError.UNIMPLEMENTED); + break; + case INS_SEND_ROT_DATA_CMD: + processSendRootOfTrust(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (KMException exception) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private void processGetRootOfTrustChallenge(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + // Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of + // trust data. + seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16); + kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16); + short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16); + short arr = KMArray.instance((short) 2); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, challenge); + sendOutgoing(apdu, arr); + } + + private short sendRootOfTrustCmd(APDU apdu) { + short arrInst = KMArray.instance((short) 4); + short headers = KMCoseHeaders.exp(); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short semanticTag = KMSemanticTag.exp(arrInst); + short arr = KMArray.exp(semanticTag); + return receiveIncoming(apdu, arr); + } + + private void processSendRootOfTrust(APDU apdu) { + byte[] scratchPad = apdu.getBuffer(); + short cmd = KMType.INVALID_VALUE; + // As per VTS if the input data is empty or not well-formed + // CoseMac return VERIFICATION_FAILED error. + try { + cmd = sendRootOfTrustCmd(apdu); + } catch (Exception e) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + short semanticTag = KMArray.cast(cmd).get((short) 0); + short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr(); + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + // Validate the Mac + short len = kmDataStore.getChallenge(scratchPad, (short) 0); + short extAad = KMByteBlob.instance(scratchPad, (short) 0, len); + // Compute CoseMac Structure and compare the macs. + short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + extAad, + rotPayload); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + if (!seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + // Store the data only once after reboot. + // Allow set boot params only when the host device reboots and the applet is in + // active state. If host does not support boot signal event, then allow this + // instruction any time. + kmDataStore.getDeviceBootStatus(scratchPad, (short) 0); + if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) { + // store the data. + storeRootOfTrust(rotPayload, scratchPad); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS); + } + // Invalidate the challenge + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16); + sendResponse(apdu, KMError.OK); + } + + private void storeRootOfTrust(short rotPayload, byte[] scratchPad) { + short byteBlobExp = KMByteBlob.exp(); + short intExp = KMInteger.exp(); + short boolExp = KMSimpleValue.exp(); + short arr = KMArray.instance((short) 5); + KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key. + KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked. + KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State. + KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash. + KMArray.cast(arr).add((short) 4, intExp); // Boot patch level + short semanticExp = KMSemanticTag.exp(arr); + + short semanticPtr = + decoder.decode( + semanticExp, + KMByteBlob.cast(rotPayload).getBuffer(), + KMByteBlob.cast(rotPayload).getStartOff(), + KMByteBlob.cast(rotPayload).length()); + short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr(); + // Store verified boot key + short ptr = KMArray.cast(rotArr).get((short) 0); + kmDataStore.setBootKey( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store Boot device locked. + ptr = KMArray.cast(rotArr).get((short) 1); + kmDataStore.setDeviceLocked( + (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false); + // Store verified boot state + ptr = KMArray.cast(rotArr).get((short) 2); + kmDataStore.setBootState(KMInteger.cast(ptr).getShort()); + // Store Verified boot hash + ptr = KMArray.cast(rotArr).get((short) 3); + kmDataStore.setVerifiedBootHash( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store boot patch level + ptr = KMArray.cast(rotArr).get((short) 4); + kmDataStore.setBootPatchLevel( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + } + + // After every device boot, the Keymaster becomes ready to execute all the commands only after + // 1. boot parameters are set, + // 2. system properties are set and + // 3. computed the shared secret successfully. + private boolean isKeyMintReady(byte apduIns) { + if (kmDataStore.isDeviceReady()) { + return true; + } + // Below commands are allowed even if the Keymaster is not ready. + switch (apduIns) { + case INS_GET_HW_INFO_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_ADD_RNG_ENTROPY_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_COMPUTE_SHARED_HMAC_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_INIT_STRONGBOX_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_SEND_ROT_DATA_CMD: + return true; + default: + break; + } + return false; + } + + private void generateUniqueOperationHandle(byte[] buf, short offset, short len) { + do { + seProvider.newRandomNumber(buf, offset, len); + } while (null != findOperation(buf, offset, len)); + } + + private void freeOperations() { + if (data[OP_HANDLE] != KMType.INVALID_VALUE) { + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op != null) { + releaseOperation(op); + } + } + } + + private void processEarlyBootEndedCmd(APDU apdu) { + kmDataStore.setEarlyBootEndedStatus(true); + sendResponse(apdu, KMError.OK); + } + + private short deviceLockedCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short ptr = KMVerificationToken.exp(); + // passwordOnly + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + // verification token + KMArray.cast(cmd).add((short) 1, ptr); + return receiveIncoming(apdu, cmd); + } + + private void processDeviceLockedCmd(APDU apdu) { + short cmd = deviceLockedCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short passwordOnly = KMArray.cast(cmd).get((short) 0); + short verToken = KMArray.cast(cmd).get((short) 1); + passwordOnly = KMInteger.cast(passwordOnly).getByte(); + validateVerificationToken(verToken, scratchPad); + short verTime = KMVerificationToken.cast(verToken).getTimestamp(); + short lastDeviceLockedTime; + try { + lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp(); + } catch (KMException e) { + lastDeviceLockedTime = KMInteger.uint_8((byte) 0); + } + if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0); + KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64); + kmDataStore.setDeviceLock(true); + kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01); + kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64); + } + sendResponse(apdu, KMError.OK); + } + + private void resetWrappingKey() { + if (!isValidWrappingKey()) { + return; + } + Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0); + wrappingKey[0] = -1; + } + + private boolean isValidWrappingKey() { + return wrappingKey[0] != -1; + } + + private short getWrappingKey() { + return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE); + } + + private void setWrappingKey(short key) { + if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + wrappingKey[0] = 0; + Util.arrayCopyNonAtomic( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + wrappingKey, + (short) 1, + WRAPPING_KEY_SIZE); + } + + protected void resetTransientBuffers() { + short index = 0; + while (index < data.length) { + data[index] = KMType.INVALID_VALUE; + index++; + } + index = 0; + while (index < tmpVariables.length) { + tmpVariables[index] = KMType.INVALID_VALUE; + index++; + } + } + + public void sendOutgoing( + APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) { + // This is the special case where the output is encoded manually without using + // the encoder algorithm. Encoder creates a duplicate copy for each KMType Object. + // The output of the generateKey, importKey and importWrappedKey commands are huge so + // by manually encoding we can avoid duplicate copies. + // The output data is directly written to the end of heap in the below order + // output = [ + // errorCode : uint // ErrorCode + // keyBlob : bstr // KeyBlob. + // keyChars + // certifcate + // ] + // certificate = [ + // x509_cert : bstr // X509 certificate + // ] + // keyChars = { // Map + // } + byte[] buffer = repository.getHeap(); + + if (cert == null) { + // This happens for Symmetric keys. + short bufferStart = repository.allocReclaimableMemory((short) 1); + buffer[bufferStart] = (byte) 0x80; // Array of 0 length. + } else { + // Encode the certificate into cbor data at the end of the heap + // certData = [ + // x509_cert : bstr // X509 certificate + // ] + short bufferStart = + encoder.encodeCert( + repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength()); + // reclaim the unused memory in the certificate. + repository.reclaimMemory((short) (bufferStart - certStart)); + } + + // Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE] + encodeKeyCharacteristics(keyChars); + // and encode it to the end of the buffer before KEY_CHARACTERISTICS + encodeKeyBlob(keyblob); + // Write Array header and ErrorCode before data[KEY_BLOB] + short bufferStartOffset = repository.allocReclaimableMemory((short) 2); + Util.setShort(buffer, bufferStartOffset, (short) 0x8400); + + short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset); + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + private void processGetHwInfoCmd(APDU apdu) { + // No arguments expected + final byte version = 2; + // 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 + // 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(), + ckdfLable, + (short) 0, + (short) ckdfLable.length, + repository.getHeap(), + concateBuffer, + bufferIndex, + scratchPad, + (short) 0); + + // persist the computed hmac key. + kmDataStore.createComputedHmacKey(scratchPad, (short) 0, keyLen); + // Generate sharingKey verification signature and store that in scratch pad. + // tmpVariables[5] + short signLen = + seProvider.hmacSign( + scratchPad, + (short) 0, + keyLen, + sharingCheck, + (short) 0, + (short) sharingCheck.length, + scratchPad, + keyLen); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.NEGOTIATED_SHARED_SECRET_SUCCESS); + // verification signature blob - 32 bytes + // tmpVariables[1] + short signature = KMByteBlob.instance(scratchPad, keyLen, signLen); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, signature); + sendOutgoing(apdu, resp); + } + + private short upgradeKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short keyParams = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Key Blob + KMArray.cast(cmd).add((short) 1, keyParams); // Key Params + return receiveIncoming(apdu, cmd); + } + + private boolean isKeyUpgradeRequired( + short keyBlob, short appId, short appData, byte[] scratchPad) { + // Check if the KeyBlob is compatible. If there is any change in the KeyBlob, the version + // Parameter in the KeyBlob should be updated to the next version. + short version = readKeyBlobVersion(keyBlob); + parseEncryptedKeyBlob(keyBlob, appId, appData, scratchPad, version); + if (version < KEYBLOB_CURRENT_VERSION) { + return true; + } + short bootPatchLevel = kmDataStore.getBootPatchLevel(); + // Fill the key-value properties in the scratchpad + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + Util.setShort(scratchPad, (short) 0, KMType.OS_VERSION); + Util.setShort(scratchPad, (short) 2, kmDataStore.getOsVersion()); + Util.setShort(scratchPad, (short) 4, KMType.OS_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 6, kmDataStore.getOsPatch()); + Util.setShort(scratchPad, (short) 8, KMType.VENDOR_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 10, kmDataStore.getVendorPatchLevel()); + Util.setShort(scratchPad, (short) 12, KMType.BOOT_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 14, bootPatchLevel); + short index = 0; + short tag; + short systemParam; + boolean isKeyUpgradeRequired = false; + while (index < 16) { + tag = Util.getShort(scratchPad, index); + systemParam = Util.getShort(scratchPad, (short) (index + 2)); + // validate the tag and check if key needs upgrade. + short tagValue = KMKeyParameters.findTag(KMType.UINT_TAG, tag, data[HW_PARAMETERS]); + tagValue = KMIntegerTag.cast(tagValue).getValue(); + short zero = KMInteger.uint_8((byte) 0); + if (tagValue != KMType.INVALID_VALUE) { + // OS version in key characteristics must be less the OS version stored in Javacard or the + // stored version must be zero. Then only upgrade is allowed else it is invalid argument. + if ((tag == KMType.OS_VERSION + && KMInteger.compare(tagValue, systemParam) == 1 + && KMInteger.compare(systemParam, zero) == 0)) { + // Key needs upgrade. + isKeyUpgradeRequired = true; + } else if ((KMInteger.compare(tagValue, systemParam) == -1)) { + // Each os version or patch level associated with the key must be less than it's + // corresponding value stored in Javacard, then only upgrade is allowed otherwise it + // is invalid argument. + isKeyUpgradeRequired = true; + } else if (KMInteger.compare(tagValue, systemParam) == 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index += 4; + } + return isKeyUpgradeRequired; + } + + private void processUpgradeKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = upgradeKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + + short keyBlob = KMArray.cast(cmd).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + short appId = getApplicationId(data[KEY_PARAMETERS]); + short appData = getApplicationData(data[KEY_PARAMETERS]); + + data[KEY_BLOB] = KMType.INVALID_VALUE; + // Check if the KeyBlob requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself, but if there is a difference in the KeyBlob version isKeyUpgradeRequired() + // does not parse the KeyBlob. + boolean isKeyUpgradeRequired = isKeyUpgradeRequired(keyBlob, appId, appData, scratchPad); + if (isKeyUpgradeRequired) { + // copy origin + data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]); + byte keyType = getKeyType(data[HW_PARAMETERS]); + switch (keyType) { + case ASYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + break; + case SYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(SYM_KEY_BLOB_SIZE_V2_V3); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + // Update the system properties to the latest values and also re-create the KeyBlob's + // KeyCharacteristics to make sure all the values are up-to-date with the latest applet + // changes. + upgradeKeyBlobKeyCharacteristics(data[HW_PARAMETERS], scratchPad); + // create new key blob with current os version etc. + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } else { + data[KEY_BLOB] = KMByteBlob.instance((short) 0); + } + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); + sendOutgoing(apdu, resp); + } + + private void processExportKeyCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) { + // Read App Id and App Data if any from un wrapping key params + data[APP_ID] = getApplicationId(wrapParams); + data[APP_DATA] = getApplicationData(wrapParams); + data[KEY_PARAMETERS] = wrapParams; + data[KEY_BLOB] = keyBlob; + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + validateWrappingKeyBlob(); + } + + private void validateWrappingKeyBlob() { + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest + // SHA2_256. + KMTag.assertPresence( + data[SB_PARAMETERS], + KMType.ENUM_TAG, + KMType.ALGORITHM, + KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + } + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, data[HW_PARAMETERS])) { + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + + // Check that the digest and padding mode specified in unwrapping parameters are SHA2_256 + // and RSA_OAEP respectively. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + + private short decryptTransportKey( + short privExp, short modulus, short transportKey, byte[] scratchPad) { + short length = + seProvider.rsaDecipherOAEP256( + KMByteBlob.cast(privExp).getBuffer(), + KMByteBlob.cast(privExp).getStartOff(), + KMByteBlob.cast(privExp).length(), + KMByteBlob.cast(modulus).getBuffer(), + KMByteBlob.cast(modulus).getStartOff(), + KMByteBlob.cast(modulus).length(), + KMByteBlob.cast(transportKey).getBuffer(), + KMByteBlob.cast(transportKey).getStartOff(), + KMByteBlob.cast(transportKey).length(), + scratchPad, + (short) 0); + return KMByteBlob.instance(scratchPad, (short) 0, length); + } + + private void unmask(short data, short maskingKey) { + short dataLength = KMByteBlob.cast(data).length(); + short maskLength = KMByteBlob.cast(maskingKey).length(); + // Length of masking key and transport key must be same. + if (maskLength != dataLength) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (index < maskLength) { + short var1 = (short) (((short) KMByteBlob.cast(maskingKey).get(index)) & 0x00FF); + short var2 = (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF); + KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2)); + index++; + } + } + + private short beginImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Masking Key + params = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params + return receiveIncoming(apdu, cmd); + } + + private void processBeginImportWrappedKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginImportWrappedKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + // Step -1 parse the wrapping key blob + // read wrapping key blob + short keyBlob = KMArray.cast(cmd).get((short) 1); + // read un wrapping key params + short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3); + processWrappingKeyBlob(keyBlob, wrappingKeyParameters, scratchPad); + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + short transportKey = + decryptTransportKey( + data[SECRET], data[PUB_KEY], KMArray.cast(cmd).get((short) 0), scratchPad); + // Step 3 - XOR the decrypted AES-GCM key with with masking key + unmask(transportKey, KMArray.cast(cmd).get((short) 2)); + if (isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + setWrappingKey(transportKey); + sendResponse(apdu, KMError.OK); + } + + private short aesGCMEncrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + short len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length()); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short aesGCMDecrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + } + + private short finishImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 8); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid + KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid + return receiveIncoming(apdu, cmd); + } + + // TODO remove cmd later on + private void processFinishImportWrappedKeyCmd(APDU apdu) { + short cmd = finishImportWrappedKeyCmd(apdu); + short keyParameters = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + keyFmt = KMEnum.cast(keyFmt).getVal(); + validateImportKey(keyParameters, keyFmt); + byte[] scratchPad = apdu.getBuffer(); + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); + data[NONCE] = KMArray.cast(cmd).get((short) 4); + data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5); + + if (!isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + data[IMPORTED_KEY_BLOB] = + aesGCMDecrypt( + getWrappingKey(), + data[INPUT_DATA], + data[NONCE], + data[AUTH_DATA], + data[AUTH_TAG], + scratchPad); + resetWrappingKey(); + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = keyParameters; + // create key blob array + importKey(apdu, keyFmt, scratchPad); + } + + private KMAttestationCert makeCommonCert(byte[] scratchPad) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + cert.subjectName(subject); + // Validity period must be specified + short notBefore = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.cast(notBefore).getValue(); + short notAfter = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.cast(notAfter).getValue(); + // VTS sends notBefore == Epoch. + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); + short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8); + short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length); + if (KMInteger.compare(notBefore, epoch) == 0) { + cert.notBefore( + KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad); + } else { + cert.notBefore(notBefore, false, scratchPad); + } + // VTS sends notAfter == Dec 31st 9999 + if (KMInteger.compare(notAfter, end) == 0) { + cert.notAfter( + KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad); + } else { + cert.notAfter(notAfter, false, scratchPad); + } + // Serial number + short serialNum = + KMKeyParameters.findTag( + KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]); + if (serialNum != KMType.INVALID_VALUE) { + serialNum = KMBignumTag.cast(serialNum).getValue(); + } else { + serialNum = KMByteBlob.instance((short) 1); + KMByteBlob.cast(serialNum).add((short) 0, (byte) 1); + } + cert.serialNumber(serialNum); + return cert; + } + + private KMAttestationCert makeAttestationCert( + short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + + // Read App Id and App Data. + short appId = getApplicationId(attKeyParam); + short appData = getApplicationData(attKeyParam); + // Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR + // and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function. + short origBlob = data[KEY_BLOB]; + short pubKey = data[PUB_KEY]; + short privKey = data[SECRET]; + short hwParams = data[HW_PARAMETERS]; + short keyChars = data[KEY_CHARACTERISTICS]; + short customTags = data[CUSTOM_TAGS]; + // Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside + // isKeyUpgradeRequired function itself. + if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // Get the private key of the attest key. + short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET); + // Get the KeyCharacteristics and SB param of the attest key + short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS); + short attestKeySbParams = + KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced(); + // If the attest key's purpose is not "attest key" then error. + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams); + if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + short length = 0; + 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) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + // Return INVALID_TAG if Attestation IDs does not match. + if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length()) + || (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(attIdTagValue).getBuffer(), + KMByteBlob.cast(attIdTagValue).getStartOff(), + storedAttIdLen))) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen); + cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); + } + index++; + } + } + + private void processDestroyAttIdsCmd(APDU apdu) { + kmDataStore.deleteAttestationIds(); + sendResponse(apdu, KMError.OK); + } + + private void processVerifyAuthorizationCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private short abortOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAbortOperationCmd(APDU apdu) { + short cmd = abortOperationCmd(apdu); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE); + } else { + releaseOperation(op); + sendResponse(apdu, KMError.OK); + } + } + + private short finishOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); // auth token + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 4, verToken); // time stamp token + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token + return receiveIncoming(apdu, cmd); + } + + private void processFinishOperationCmd(APDU apdu) { + short cmd = finishOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[SIGNATURE] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); + data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // Authorize the finish operation + authorizeUpdateFinishOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + finishTrustedConfirmationOperation(op); + case KMType.VERIFY: + finishSigningVerifyingOperation(op, scratchPad); + break; + case KMType.ENCRYPT: + finishEncryptOperation(op, scratchPad); + break; + case KMType.DECRYPT: + finishDecryptOperation(op, scratchPad); + break; + case KMType.AGREE_KEY: + finishKeyAgreementOperation(op, scratchPad); + break; + } + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Remove the operation handle + releaseOperation(op); + + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { + if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + finishAesDesOperation(op); + } + + private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch (op.getAlgorithm()) { + case KMType.RSA: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + if (op.getPadding() == KMType.PADDING_NONE && len != 256) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + break; + case KMType.AES: + case KMType.DES: + finishAesDesOperation(op); + break; + } + } + + private void finishAesDesOperation(KMOperationState op) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = AES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.DES) { + blockSize = DES_BLOCK_SIZE; + } + + if (op.getPurpose() == KMType.DECRYPT + && len > 0 + && (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC) + && ((short) (len % blockSize) != 0)) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + if (op.getBlockMode() == KMType.GCM) { + if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + // Get the output size + len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8)); + } + // If padding i.e. pkcs7 then add padding to right + // Output data can at most one block size more the input data in case of pkcs7 encryption + // In case of gcm we will allocate extra memory of the size equal to blocksize. + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + try { + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + if (e.getReason() == CryptoException.ILLEGAL_USE) { + // As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length + // output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is + // provided via update() method. So ignore this exception in case if all below conditions + // are satisfied and simply return empty output. + // 1. padding mode is PADDING_NONE. + // 2. No input message is processed in update(). + // 3. Zero length input data is passed in finish operation. + if ((op.getPadding() == KMType.PADDING_NONE) + && !op.isInputMsgProcessed() + && (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) { + len = 0; + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + } + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = + op.getOperation() + .finish( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.cast(blob).length(), + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + switch (op.getAlgorithm()) { + case KMType.RSA: + // If there is no padding we can treat signing as a RSA decryption operation. + try { + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 256 bytes - but it can be less then 256 bytes + short len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + // Maximum output size of signature is 256 bytes. - the signature will always be + // positive + data[OUTPUT_DATA] = KMByteBlob.instance((short) 256); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + (short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len), + len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.EC: + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + // If DIGEST NONE then truncate the input data to 32 bytes. + if (op.getDigest() == KMType.DIGEST_NONE && len > 32) { + len = 32; + } + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 512 bits i.e. 64 bytes + len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + if (op.getPurpose() == KMType.SIGN) { + // Copy only signature of mac length size. + data[OUTPUT_DATA] = + KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8)); + } else if (op.getPurpose() == KMType.VERIFY) { + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8)) + || KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + + if (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + break; + default: // This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } + } + + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // If one time user Authentication is required + if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { + // Validate Verification Token. + validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); + // validate operation handle. + short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + if (KMInteger.compare(ptr, op.getHandle()) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + tmpVariables[0] = op.getAuthTime(); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + if (!authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + } + + private void authorizeKeyUsageForCount(byte[] scratchPad) { + short scratchPadOff = 0; + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0); + + short usageLimitBufLen = + KMIntegerTag.getValue( + scratchPad, + scratchPadOff, + KMType.UINT_TAG, + KMType.MAX_USES_PER_BOOT, + data[HW_PARAMETERS]); + + if (usageLimitBufLen == KMType.INVALID_VALUE) { + return; + } + + if (usageLimitBufLen > 4) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) { + // Get current counter, update and increment it. + short len = + kmDataStore.getRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4)); + if (len != 4) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (0 + >= KMInteger.unsignedByteArrayCompare( + scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) { + KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED); + } + // Increment the counter. + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0); + Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1); + KMUtils.add( + scratchPad, + scratchPadOff, + (short) (scratchPadOff + len), + (short) (scratchPadOff + len * 2)); + + kmDataStore.setRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len); + } else { + // Persist auth tag. + if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + } + } + + private void authorizeDeviceUnlock(byte[] scratchPad) { + // If device is locked and key characteristics requires unlocked device then check whether + // HW auth token has correct timestamp. + short ptr = + KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]); + + if (ptr != KMType.INVALID_VALUE && kmDataStore.getDeviceLock()) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(); + // Check if the current auth time stamp is greater than device locked time stamp + short ts = kmDataStore.getDeviceTimeStamp(); + if (KMInteger.compare(ptr, ts) <= 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + // Now check if the device unlock requires password only authentication and whether + // auth token is generated through password authentication or not. + if (kmDataStore.getDeviceLockPasswordOnly()) { + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + } + // Unlock the device + // repository.deviceLockedFlag = false; + kmDataStore.setDeviceLock(false); + kmDataStore.clearDeviceLockTimeStamp(); + } + } + + private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopyNonAtomic( + authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; + // concatenate challenge - 8 bytes + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate security level - 4 bytes + scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; + len += KMInteger.UINT_32; + // hmac the data + ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private void validateVerificationToken(short verToken, byte[] scratchPad) { + short ptr = KMVerificationToken.cast(verToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private short updateOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateOperationCmd(APDU apdu) { + short cmd = updateOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { + // update the data. + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + // update trusted confirmation operation + updateTrustedConfirmationOperation(op); + + data[OUTPUT_DATA] = KMType.INVALID_VALUE; + } else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + // Update for encrypt/decrypt using RSA will not be supported because to do this op state + // will have to buffer the data - so reject the update if it is rsa algorithm. + if (op.getAlgorithm() == KMType.RSA) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = DES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.AES) { + blockSize = AES_BLOCK_SIZE; + if (op.getBlockMode() == KMType.GCM) { + // if input data present + if (len > 0) { + // no more future updateAAD allowed if input data present. + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + } + } + } + // Allocate output buffer as input data is already block aligned + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + // Otherwise just update the data. + // HAL consumes all the input and maintains a buffered data inside it. So the + // applet sends the inputConsumed length as same as the input length. + try { + len = + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) { + // This flag is used to denote that an input data of length > 0 is received and processed + // successfully in update command. This flag is later used in the finish operation + // to handle a particular use case, where a zero length input data on AES/DES algorithm + // with PADDING_NONE should return a zero length output with OK response. + op.setProcessedInputMsg(true); + } + // Adjust the Output data if it is not equal to input data. + // This happens in case of JCardSim provider. + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Persist if there are any updates. + // op.persist(); + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private short updateAadOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateAadOperationCmd(APDU apdu) { + short cmd = updateAadOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + if (op.getAlgorithm() != KMType.AES) { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + if (op.getBlockMode() != KMType.GCM) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + try { + op.getOperation() + .updateAAD( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make response + short resp = KMArray.instance((short) 1); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + sendOutgoing(apdu, resp); + } + + private short beginOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, params); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); + return receiveIncoming(apdu, cmd); + } + + private void processBeginOperationCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short purpose = KMArray.cast(cmd).get((short) 0); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + purpose = KMEnum.cast(purpose).getVal(); + // Check for app id and app data. + data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]); + data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]); + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + KMTag.assertPresence( + data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM); + short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]); + // If Blob usage tag is present in key characteristics then it should be standalone. + if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) { + if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + } + + // Generate a random number for operation handle + short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE); + generateUniqueOperationHandle( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + /* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */ + short opHandle = + KMInteger.instance( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + KMOperationState op = reserveOperation(algorithm, opHandle); + if (op == null) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + data[OP_HANDLE] = op.getHandle(); + op.setPurpose((byte) purpose); + op.setKeySize(KMByteBlob.cast(data[SECRET]).length()); + authorizeAndBeginOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + beginTrustedConfirmationOperation(op); + case KMType.VERIFY: + beginSignVerifyOperation(op); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + beginCipherOperation(op); + break; + case KMType.AGREE_KEY: + beginKeyAgreementOperation(op); + break; + default: + KMException.throwIt(KMError.UNIMPLEMENTED); + break; + } + short iv = KMType.INVALID_VALUE; + // If the data[IV] is required to be returned. + // As per VTS, for the decryption operation don't send the iv back. + if (data[IV] != KMType.INVALID_VALUE + && op.getPurpose() != KMType.DECRYPT + && op.getBlockMode() != KMType.ECB) { + iv = KMArray.instance((short) 1); + if (op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) { + // For AES/DES we are generate an random iv of length 16 bytes. + // While sending the iv back for DES/CBC mode of opeation only send + // 8 bytes back. + short ivBlob = KMByteBlob.instance((short) 8); + Util.arrayCopy( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(ivBlob).getBuffer(), + KMByteBlob.cast(ivBlob).getStartOff(), + (short) 8); + data[IV] = ivBlob; + } + KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + } else { + iv = KMArray.instance((short) 0); + } + short macLen = 0; + if (op.getMacLength() != KMType.INVALID_VALUE) { + macLen = (short) (op.getMacLength() / 8); + } + short params = KMKeyParameters.instance(iv); + short resp = KMArray.instance((short) 5); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + KMArray.cast(resp).add((short) 2, data[OP_HANDLE]); + KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode())); + KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen)); + sendOutgoing(apdu, resp); + } + + private void authorizeAlgorithm(KMOperationState op) { + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]); + if (alg == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + op.setAlgorithm((byte) alg); + } + + 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); + } + if (macLen % 8 != 0 + || macLen > 128 + || macLen + < KMIntegerTag.getShortValue( + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + 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]); + op.setMinMacLength(minMacLen); + if (macLen == KMType.INVALID_VALUE) { + if (op.getPurpose() == KMType.SIGN) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + } else { + // MAC length may not be specified for verify. + if (op.getPurpose() == KMType.VERIFY) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (macLen % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + data[HW_TOKEN] = KMType.INVALID_VALUE; + } + authorizeUserSecureIdAuthTimeout(op, scratchPad); + authorizeDeviceUnlock(scratchPad); + authorizeKeyUsageForCount(scratchPad); + + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB); + + // Validate early boot + // VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is + // present + if (kmDataStore.getEarlyBootEndedStatus()) { + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + } + + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail if it is not a Decrypt operation + data[IV] = KMType.INVALID_VALUE; + + if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE) + && KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE) + && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + + short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + // If Nonce is present then check whether the size of nonce is correct. + if (nonce != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(nonce).getValue(); + // For CBC mode - iv must be 8 bytes + if (op.getBlockMode() == KMType.CBC + && op.getAlgorithm() == KMType.DES + && KMByteBlob.cast(data[IV]).length() != 8) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For GCM mode - IV must be 12 bytes + if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For AES CBC and CTR modes IV must be 16 bytes + if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR) + && op.getAlgorithm() == KMType.AES + && KMByteBlob.cast(data[IV]).length() != 16) { + KMException.throwIt(KMError.INVALID_NONCE); + } + } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { + + // For symmetric decryption iv is required + if (op.getPurpose() == KMType.DECRYPT + && (op.getBlockMode() == KMType.CBC + || op.getBlockMode() == KMType.GCM + || op.getBlockMode() == KMType.CTR)) { + KMException.throwIt(KMError.MISSING_NONCE); + } else if (op.getBlockMode() == KMType.ECB) { + // For ECB we create zero length nonce + data[IV] = KMByteBlob.instance((short) 0); + } else if (op.getPurpose() == KMType.ENCRYPT) { + + // For encrypt mode if nonce is absent then create random nonce of correct length + byte ivLen = 16; + if (op.getBlockMode() == KMType.GCM) { + ivLen = 12; + } else if (op.getAlgorithm() == KMType.DES) { + ivLen = 8; + } + data[IV] = KMByteBlob.instance(ivLen); + seProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length()); + } + } + } + + private void beginKeyAgreementOperation(KMOperationState op) { + if (op.getAlgorithm() != KMType.EC) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF1 Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } + + private void beginCipherOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.DECRYPT) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + (byte) op.getMgfDigest(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.AES: + case KMType.DES: + if (op.getBlockMode() == KMType.GCM) { + op.setAesGcmUpdateStart(); + } + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length(), + op.getMacLength())); + } catch (CryptoException exception) { + if (exception.getReason() == CryptoException.ILLEGAL_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } else if (exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + } + + private void beginTrustedConfirmationOperation(KMOperationState op) { + // Check for trusted confirmation - if required then set the signer in op state. + if (KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS]) + != KMType.INVALID_VALUE) { + + op.setTrustedConfirmationSigner( + seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey())); + + op.getTrustedConfirmationSigner() + .update(confirmationToken, (short) 0, (short) confirmationToken.length); + } + } + + private void beginSignVerifyOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.EC: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. So this is the reason we are using KMType.SIGN directly + // instead of using op.getPurpose(). + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) KMType.SIGN, + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0, + (short) 0)); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + } + + private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) { + short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); + if (!KMInteger.cast(secureUserId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) { + return true; + } + } + + short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); + if (!KMInteger.cast(authenticatorId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) { + return true; + } + } + return false; + } + + private boolean authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + return false; + } + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { + return false; + } + // check auth type + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) authType) == 0) { + return false; + } + return true; + } + + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { + short authTime; + short authType; + // Authorize User Secure Id and Auth timeout + short userSecureIdPtr = + KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + if (userSecureIdPtr != KMType.INVALID_VALUE) { + // Authentication required. + if (KMType.INVALID_VALUE + != KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // authenticator type must be provided. + if (KMType.INVALID_VALUE + == (authType = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { + // authenticate user + if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + authTimeoutTagPtr = + KMKeyParameters.findTag( + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]); + if (authTimeoutTagPtr == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); + // set the one time auth + op.setOneTimeAuthReqd(true); + // set the authentication time stamp in operation state + authTime = + addIntegers( + authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); + op.setAuthTime( + KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + // auth time validation will happen in update or finish + op.setAuthTimeoutValidated(false); + } else { + // auth per operation required + // store user secure id and authType in OperationState. + op.setUserSecureId(userSecureIdPtr); + op.setAuthType((byte) authType); + // set flags + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId, authenticatorType and timestamp + // are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId values are in little endian order, + // but authenticatorType and timestamp are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return false; + } + if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) { + return verifyHwTokenMacInLittleEndian(hwToken, scratchPad); + } else { + return verifyHwTokenMacInBigEndian(hwToken, scratchPad); + } + } + + private short importKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key + KMArray.cast(cmd).add((short) 4, params); // attest key params + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processImportKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = importKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5); + keyFmt = KMEnum.cast(keyFmt).getVal(); + + data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty. + data[ORIGIN] = KMType.IMPORTED; + 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); + byte[] scratchPad = apdu.getBuffer(); + + short osVersion = KMArray.cast(cmd).get((short) 0); + short osPatchLevel = KMArray.cast(cmd).get((short) 1); + short vendorPatchLevel = KMArray.cast(cmd).get((short) 2); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS); + } + + public void reboot() { + // flag to maintain early boot ended state + kmDataStore.setEarlyBootEndedStatus(false); + // Clear all the operation state. + releaseAllOperations(); + // Hmac is cleared, so generate a new Hmac nonce. + initHmacNonceAndSeed(); + // Clear all auth tags. + kmDataStore.removeAllAuthTags(); + } + + protected void initSystemBootParams( + short osVersion, short osPatchLevel, short vendorPatchLevel, short bootPatchLevel) { + osVersion = KMInteger.uint_16(osVersion); + osPatchLevel = KMInteger.uint_16(osPatchLevel); + vendorPatchLevel = KMInteger.uint_16((short) vendorPatchLevel); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + } + + protected void setOsVersion(short version) { + kmDataStore.setOsVersion( + KMInteger.cast(version).getBuffer(), + KMInteger.cast(version).getStartOff(), + KMInteger.cast(version).length()); + } + + protected void setOsPatchLevel(short patch) { + kmDataStore.setOsPatch( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + protected void setVendorPatchLevel(short patch) { + kmDataStore.setVendorPatchLevel( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + private short generateKeyCmd(APDU apdu) { + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, params); // key params + KMArray.cast(cmd).add((short) 1, blob); // attest key + KMArray.cast(cmd).add((short) 2, params); // attest key params + KMArray.cast(cmd).add((short) 3, blob); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processGenerateKey(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = generateKeyCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3); + data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty. + // ROLLBACK_RESISTANCE not supported. + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + + // Algorithm must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + 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. + // Device unique attestation 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: + 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); + } + } + + 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); + } + + 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; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java new file mode 100644 index 0000000..3de1d0c --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -0,0 +1,1050 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMComputedHmacKey; +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMDeviceUniqueKeyPair; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMMasterKey; +import com.android.javacard.seprovider.KMPreSharedKey; +import com.android.javacard.seprovider.KMRkpMacKey; +import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.seprovider.KMUpgradable; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import org.globalplatform.upgrade.Element; + +public class KMKeymintDataStore implements KMUpgradable { + + // Data table configuration + public static final short KM_APPLET_PACKAGE_VERSION_1 = 0x0100; + public static final short KM_APPLET_PACKAGE_VERSION_2 = 0x0200; + public static final short KM_APPLET_PACKAGE_VERSION_3 = 0x0300; + public static final byte DATA_INDEX_SIZE = 17; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_LENGTH = 0; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + public static final short DATA_MEM_SIZE = 300; + // Data table offsets + public static final byte HMAC_NONCE = 0; + public static final byte BOOT_OS_VERSION = 1; + public static final byte BOOT_OS_PATCH_LEVEL = 2; + public static final byte VENDOR_PATCH_LEVEL = 3; + public static final byte DEVICE_LOCKED_TIME = 4; + public static final byte DEVICE_LOCKED = 5; + public static final byte DEVICE_LOCKED_PASSWORD_ONLY = 6; + // Total 8 auth tags, so the next offset is AUTH_TAG_1 + 8 + public static final byte AUTH_TAG_1 = 7; + public static final byte DEVICE_STATUS_FLAG = 15; + public static final byte EARLY_BOOT_ENDED_FLAG = 16; + // Data Item sizes + public static final byte HMAC_SEED_NONCE_SIZE = 32; + public static final byte COMPUTED_HMAC_KEY_SIZE = 32; + public static final byte OS_VERSION_SIZE = 4; + public static final byte OS_PATCH_SIZE = 4; + public static final byte VENDOR_PATCH_SIZE = 4; + public static final byte DEVICE_LOCK_TS_SIZE = 8; + public static final byte MAX_BLOB_STORAGE = 8; + public static final byte AUTH_TAG_LENGTH = 16; + public static final byte AUTH_TAG_COUNTER_SIZE = 4; + public static final byte AUTH_TAG_ENTRY_SIZE = (AUTH_TAG_LENGTH + AUTH_TAG_COUNTER_SIZE + 1); + // Device boot states. Applet starts executing the + // core commands once all the states are set. The commands + // that are allowed irrespective of these states are: + // All the provision commands + // INS_GET_HW_INFO_CMD + // INS_ADD_RNG_ENTROPY_CMD + // INS_COMPUTE_SHARED_HMAC_CMD + // INS_GET_HMAC_SHARING_PARAM_CMD + public static final byte SET_BOOT_PARAMS_SUCCESS = 0x01; + public static final byte SET_SYSTEM_PROPERTIES_SUCCESS = 0x02; + public static final byte NEGOTIATED_SHARED_SECRET_SUCCESS = 0x04; + // Old Data table offsets + private static final byte OLD_PROVISIONED_STATUS_OFFSET = 18; + private static final byte SHARED_SECRET_KEY_SIZE = 32; + private static final byte DEVICE_STATUS_FLAG_SIZE = 1; + private static final short ADDITIONAL_CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. + private static final short BCC_MAX_SIZE = 512; + private static final byte[] zero = {0, 0, 0, 0, 0, 0, 0, 0}; + private static KMKeymintDataStore kmDataStore; + // Secure Boot Mode + public byte secureBootMode; + // Data - originally was in repository + private byte[] attIdBrand; + private byte[] attIdDevice; + private byte[] attIdProduct; + private byte[] attIdSerial; + private byte[] attIdImei; + private byte[] attIdMeId; + private byte[] attIdManufacturer; + private byte[] attIdModel; + // Boot parameters + private byte[] verifiedHash; + private byte[] bootKey; + private byte[] bootPatchLevel; + private boolean deviceBootLocked; + private short bootState; + // Challenge for Root of trust + private byte[] challenge; + private short dataIndex; + private byte[] dataTable; + private KMSEProvider seProvider; + private KMRepository repository; + private byte[] additionalCertChain; + private byte[] bcc; + private KMMasterKey masterKey; + private KMDeviceUniqueKeyPair testDeviceUniqueKeyPair; + private KMDeviceUniqueKeyPair deviceUniqueKeyPair; + private KMPreSharedKey preSharedKey; + private KMComputedHmacKey computedHmacKey; + private KMRkpMacKey rkpMacKey; + private byte[] oemRootPublicKey; + private short provisionStatus; + + public KMKeymintDataStore(KMSEProvider provider, KMRepository repo) { + seProvider = provider; + repository = repo; + boolean isUpgrading = provider.isUpgrading(); + initDataTable(); + // Initialize the device locked status + if (!isUpgrading) { + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + bcc = new byte[BCC_MAX_SIZE]; + oemRootPublicKey = new byte[65]; + } + setDeviceLockPasswordOnly(false); + setDeviceLock(false); + kmDataStore = this; + } + + public static KMKeymintDataStore instance() { + return kmDataStore; + } + + private void initDataTable() { + if (dataTable == null) { + dataTable = new byte[DATA_MEM_SIZE]; + dataIndex = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + } + + private short dataAlloc(short length) { + if (((short) (dataIndex + length)) > dataTable.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex += length; + return (short) (dataIndex - length); + } + + private void clearDataEntry(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen != 0) { + short dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayFillNonAtomic(dataTable, dataPtr, dataLen, (byte) 0); + JCSystem.commitTransaction(); + } + } + + private void writeDataEntry(short id, byte[] buf, short offset, short len) { + short dataPtr; + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen == 0) { + dataPtr = dataAlloc(len); + JCSystem.beginTransaction(); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET), dataPtr); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH), len); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } else { + if (len != dataLen) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } + } + + private short readDataEntry(short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short readDataEntry(byte[] dataTable, short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short dataLength(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + } + + public short readData(short id) { + short len = dataLength(id); + if (len != 0) { + short blob = KMByteBlob.instance(dataLength(id)); + readDataEntry(id, KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + return blob; + } + return KMType.INVALID_VALUE; + } + + public short getHmacNonce() { + return readData(HMAC_NONCE); + } + + public short getOsVersion() { + short blob = readData(BOOT_OS_VERSION); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getVendorPatchLevel() { + short blob = readData(VENDOR_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getOsPatch() { + short blob = readData(BOOT_OS_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + private boolean readBoolean(short id) { + short blob = readData(id); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return (byte) ((repository.getHeap())[KMByteBlob.cast(blob).getStartOff()]) == 0x01; + } + + public boolean getDeviceLock() { + return readBoolean(DEVICE_LOCKED); + } + + public void setDeviceLock(boolean flag) { + writeBoolean(DEVICE_LOCKED, flag); + } + + public boolean getDeviceLockPasswordOnly() { + return readBoolean(DEVICE_LOCKED_PASSWORD_ONLY); + } + + public void setDeviceLockPasswordOnly(boolean flag) { + writeBoolean(DEVICE_LOCKED_PASSWORD_ONLY, flag); + } + + public boolean getEarlyBootEndedStatus() { + return readBoolean(EARLY_BOOT_ENDED_FLAG); + } + + public void setEarlyBootEndedStatus(boolean flag) { + writeBoolean(EARLY_BOOT_ENDED_FLAG, flag); + } + + public short getDeviceTimeStamp() { + short blob = readData(DEVICE_LOCKED_TIME); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_64( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public void setOsVersion(byte[] buf, short start, short len) { + if (len != OS_VERSION_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_VERSION, buf, start, len); + } + + public void setVendorPatchLevel(byte[] buf, short start, short len) { + if (len != VENDOR_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(VENDOR_PATCH_LEVEL, buf, start, len); + } + + private void writeBoolean(short id, boolean flag) { + short start = repository.alloc((short) 1); + if (flag) { + (repository.getHeap())[start] = (byte) 0x01; + } else { + (repository.getHeap())[start] = (byte) 0x00; + } + writeDataEntry(id, repository.getHeap(), start, (short) 1); + } + + public void setDeviceLockTimestamp(byte[] buf, short start, short len) { + if (len != DEVICE_LOCK_TS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(DEVICE_LOCKED_TIME, buf, start, len); + } + + public void clearDeviceBootStatus() { + clearDataEntry(DEVICE_STATUS_FLAG); + } + + public void setDeviceBootStatus(byte initStatus) { + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + buf[offset] |= initStatus; + writeDataEntry(DEVICE_STATUS_FLAG, buf, offset, DEVICE_STATUS_FLAG_SIZE); + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + } + + public boolean isDeviceReady() { + boolean result = false; + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + byte bootCompleteStatus = + (SET_BOOT_PARAMS_SUCCESS + | SET_SYSTEM_PROPERTIES_SUCCESS + | NEGOTIATED_SHARED_SECRET_SUCCESS); + if (bootCompleteStatus == (buf[offset] & bootCompleteStatus)) { + result = true; + } + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + return result; + } + + public short getDeviceBootStatus(byte[] scratchpad, short offset) { + scratchpad[offset] = 0; + return readDataEntry(DEVICE_STATUS_FLAG, scratchpad, offset); + } + + public void clearDeviceLockTimeStamp() { + clearDataEntry(DEVICE_LOCKED_TIME); + } + + public void setOsPatch(byte[] buf, short start, short len) { + if (len != OS_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_PATCH_LEVEL, buf, start, len); + } + + private boolean isAuthTagSlotAvailable(short tagId, byte[] buf, short offset) { + readDataEntry(tagId, buf, offset); + return (0 == buf[offset]); + } + + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (len != HMAC_SEED_NONCE_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(HMAC_NONCE, nonce, offset, len); + } + + public void clearHmacNonce() { + clearDataEntry(HMAC_NONCE); + } + + public boolean persistAuthTag(short authTag) { + + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + short authTagEntry = repository.alloc(AUTH_TAG_ENTRY_SIZE); + short scratchPadOff = repository.alloc(AUTH_TAG_ENTRY_SIZE); + byte[] scratchPad = repository.getHeap(); + writeAuthTagState(repository.getHeap(), authTagEntry, (byte) 1); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + repository.getHeap(), + (short) (authTagEntry + 1), + AUTH_TAG_LENGTH); + Util.setShort( + repository.getHeap(), (short) (authTagEntry + AUTH_TAG_LENGTH + 1 + 2), (short) 1); + short index = 0; + while (index < MAX_BLOB_STORAGE) { + if ((dataLength((short) (index + AUTH_TAG_1)) == 0) + || isAuthTagSlotAvailable((short) (index + AUTH_TAG_1), scratchPad, scratchPadOff)) { + + writeDataEntry( + (short) (index + AUTH_TAG_1), repository.getHeap(), authTagEntry, AUTH_TAG_ENTRY_SIZE); + return true; + } + index++; + } + return false; + } + + public void removeAllAuthTags() { + short index = 0; + while (index < MAX_BLOB_STORAGE) { + clearDataEntry((short) (index + AUTH_TAG_1)); + index++; + } + } + + public boolean isAuthTagPersisted(short authTag) { + return (KMType.INVALID_VALUE != findTag(authTag)); + } + + private short findTag(short authTag) { + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short index = 0; + short found; + short offset = repository.alloc(AUTH_TAG_ENTRY_SIZE); + while (index < MAX_BLOB_STORAGE) { + if (dataLength((short) (index + AUTH_TAG_1)) != 0) { + readDataEntry((short) (index + AUTH_TAG_1), repository.getHeap(), offset); + found = + Util.arrayCompare( + repository.getHeap(), + (short) (offset + 1), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + AUTH_TAG_LENGTH); + if (found == 0) { + return (short) (index + AUTH_TAG_1); + } + } + index++; + } + return KMType.INVALID_VALUE; + } + + public short getRateLimitedKeyCount(short authTag, byte[] out, short outOff) { + short tag = findTag(authTag); + short blob; + if (tag != KMType.INVALID_VALUE) { + blob = readData(tag); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(blob).getBuffer(), + (short) (KMByteBlob.cast(blob).getStartOff() + AUTH_TAG_LENGTH + 1), + out, + outOff, + AUTH_TAG_COUNTER_SIZE); + return AUTH_TAG_COUNTER_SIZE; + } + return (short) 0; + } + + public void setRateLimitedKeyCount(short authTag, byte[] buf, short off, short len) { + short tag = findTag(authTag); + if (tag != KMType.INVALID_VALUE) { + short dataPtr = readData(tag); + Util.arrayCopyNonAtomic( + buf, + off, + KMByteBlob.cast(dataPtr).getBuffer(), + (short) (KMByteBlob.cast(dataPtr).getStartOff() + AUTH_TAG_LENGTH + 1), + len); + writeDataEntry( + tag, + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + KMByteBlob.cast(dataPtr).length()); + } + } + + public void persistAdditionalCertChain(byte[] buf, short offset, short len) { + // Input buffer contains encoded additional certificate chain as shown below. + // AdditionalDKSignatures = { + // + SignerName => DKCertChain + // } + // SignerName = tstr + // DKCertChain = [ + // 2* Certificate // Root -> Leaf. Root is the vendo r + // // self-signed cert, leaf contains DK_pu b + // ] + // Certificate = COSE_Sign1 of a public key + if ((short) (len + 2) > ADDITIONAL_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(additionalCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, additionalCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + public short getAdditionalCertChainLength() { + return Util.getShort(additionalCertChain, (short) 0); + } + + public byte[] getAdditionalCertChain() { + return additionalCertChain; + } + + public byte[] getBootCertificateChain() { + return bcc; + } + + public void persistBootCertificateChain(byte[] buf, short offset, short len) { + if ((short) (len + 2) > BCC_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(bcc, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, bcc, (short) 2, len); + JCSystem.commitTransaction(); + } + + private void writeAuthTagState(byte[] buf, short offset, byte state) { + buf[offset] = state; + } + + public KMMasterKey createMasterKey(short keySizeBits) { + if (masterKey == null) { + masterKey = seProvider.createMasterKey(masterKey, keySizeBits); + } + return (KMMasterKey) masterKey; + } + + public KMMasterKey getMasterKey() { + return masterKey; + } + + public void createPresharedKey(byte[] keyData, short offset, short length) { + if (length != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (preSharedKey == null) { + preSharedKey = seProvider.createPreSharedKey(preSharedKey, keyData, offset, length); + } + } + + public KMPreSharedKey getPresharedKey() { + if (preSharedKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return preSharedKey; + } + + public void createComputedHmacKey(byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (computedHmacKey == null) { + computedHmacKey = seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } else { + seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } + } + + public KMComputedHmacKey getComputedHmacKey() { + if (computedHmacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return computedHmacKey; + } + + public KMDeviceUniqueKeyPair createRkpTestDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (testDeviceUniqueKeyPair == null) { + testDeviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + testDeviceUniqueKeyPair, + pubKey, + pubKeyOff, + pubKeyLen, + privKey, + privKeyOff, + privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + testDeviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return testDeviceUniqueKeyPair; + } + + public KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (deviceUniqueKeyPair == null) { + deviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return deviceUniqueKeyPair; + } + + public KMDeviceUniqueKeyPair getRkpDeviceUniqueKeyPair(boolean testMode) { + return ((KMDeviceUniqueKeyPair) (testMode ? testDeviceUniqueKeyPair : deviceUniqueKeyPair)); + } + + public void createRkpMacKey(byte[] keydata, short offset, short length) { + if (rkpMacKey == null) { + rkpMacKey = seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } else { + seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } + } + + public KMRkpMacKey getRkpMacKey() { + if (rkpMacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return rkpMacKey; + } + + public short getAttestationId(short tag, byte[] buffer, short start) { + byte[] attestId = null; + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + attestId = attIdBrand; + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + attestId = attIdDevice; + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + attestId = attIdProduct; + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + attestId = attIdSerial; + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + attestId = attIdImei; + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + attestId = attIdMeId; + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + attestId = attIdManufacturer; + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + attestId = attIdModel; + break; + } + if (attestId == null) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + Util.arrayCopyNonAtomic(attestId, (short) 0, buffer, start, (short) attestId.length); + return (short) attestId.length; + } + + public void setAttestationId(short tag, byte[] buffer, short start, short length) { + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + JCSystem.beginTransaction(); + attIdBrand = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdBrand, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + JCSystem.beginTransaction(); + attIdDevice = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdDevice, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + JCSystem.beginTransaction(); + attIdProduct = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdProduct, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + JCSystem.beginTransaction(); + attIdSerial = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSerial, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + JCSystem.beginTransaction(); + attIdImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + JCSystem.beginTransaction(); + attIdMeId = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdMeId, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + JCSystem.beginTransaction(); + attIdManufacturer = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdManufacturer, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + JCSystem.beginTransaction(); + attIdModel = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdModel, (short) 0, length); + JCSystem.commitTransaction(); + break; + } + } + + public void deleteAttestationIds() { + attIdBrand = null; + attIdDevice = null; + attIdProduct = null; + attIdSerial = null; + attIdImei = null; + attIdMeId = null; + attIdManufacturer = null; + attIdModel = null; + // Trigger garbage collection. + JCSystem.requestObjectDeletion(); + } + + public short getVerifiedBootHash(byte[] buffer, short start) { + if (verifiedHash == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(verifiedHash, (short) 0, buffer, start, (short) verifiedHash.length); + return (short) verifiedHash.length; + } + + public short getBootKey(byte[] buffer, short start) { + if (bootKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(bootKey, (short) 0, buffer, start, (short) bootKey.length); + return (short) bootKey.length; + } + + public short getBootState() { + return bootState; + } + + public void setBootState(short state) { + bootState = state; + } + + public boolean isDeviceBootLocked() { + return deviceBootLocked; + } + + public short getBootPatchLevel() { + if (bootPatchLevel == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32(bootPatchLevel, (short) 0); + } + + public void setVerifiedBootHash(byte[] buffer, short start, short length) { + if (verifiedHash == null) { + verifiedHash = new byte[32]; + } + if (length != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, verifiedHash, (short) 0, (short) 32); + } + + public void setBootKey(byte[] buffer, short start, short length) { + if (bootKey == null) { + bootKey = new byte[32]; + } + if (length != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootKey, (short) 0, (short) 32); + } + + public void setDeviceLocked(boolean state) { + deviceBootLocked = state; + } + + public void setBootPatchLevel(byte[] buffer, short start, short length) { + if (bootPatchLevel == null) { + bootPatchLevel = new byte[4]; + } + if (length > 4 || length < 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootPatchLevel, (short) 0, (short) length); + } + + public void setChallenge(byte[] buf, short start, short length) { + if (challenge == null) { + challenge = new byte[16]; + } + if (length != 16) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(buf, start, challenge, (short) 0, (short) length); + } + + public short getChallenge(byte[] buffer, short start) { + if (challenge == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(challenge, (short) 0, buffer, start, (short) challenge.length); + return (short) challenge.length; + } + + public boolean isProvisionLocked() { + if (0 != (provisionStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + return true; + } + return false; + } + + public short getProvisionStatus() { + return provisionStatus; + } + + public void setProvisionStatus(short pStatus) { + JCSystem.beginTransaction(); + provisionStatus |= pStatus; + JCSystem.commitTransaction(); + } + + public void unlockProvision() { + JCSystem.beginTransaction(); + provisionStatus &= ~KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED; + JCSystem.commitTransaction(); + } + + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (oemRootPublicKey == null) { + oemRootPublicKey = new byte[65]; + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + public byte[] getOEMRootPublicKey() { + if (oemRootPublicKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return oemRootPublicKey; + } + + @Override + public void onSave(Element element) { + // Prmitives + element.write(provisionStatus); + element.write(secureBootMode); + // Objects + element.write(attIdBrand); + element.write(attIdDevice); + element.write(attIdProduct); + element.write(attIdSerial); + element.write(attIdImei); + element.write(attIdMeId); + element.write(attIdManufacturer); + element.write(attIdModel); + element.write(additionalCertChain); + element.write(bcc); + element.write(oemRootPublicKey); + + // Key Objects + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, masterKey); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY, preSharedKey); + seProvider.onSave( + element, KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR, deviceUniqueKeyPair); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY, rkpMacKey); + } + + @Override + public void onRestore(Element element, short oldVersion, short currentVersion) { + if (oldVersion <= KM_APPLET_PACKAGE_VERSION_1) { + // 1.0 to 3.0 Upgrade happens here. + handlePreviousVersionUpgrade(element); + return; + } else if (oldVersion == KM_APPLET_PACKAGE_VERSION_2) { + handleUpgrade(element, oldVersion); + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 2.0 to 3.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + // Request for ObjectDeletion for unreferenced address of additionalCertChain. + JCSystem.requestObjectDeletion(); + return; + } + handleUpgrade(element, oldVersion); + } + + private void handlePreviousVersionUpgrade(Element element) { + // Read Primitives + // restore old data table index + short oldDataIndex = element.readShort(); + element.readBoolean(); // pop deviceBootLocked + element.readShort(); // pop bootState + + // Read Objects + // restore old data table + byte[] oldDataTable = (byte[]) element.readObject(); + + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + element.readObject(); // pop verifiedHash + element.readObject(); // pop bootKey + element.readObject(); // pop bootPatchLevel + additionalCertChain = (byte[]) element.readObject(); + bcc = (byte[]) element.readObject(); + + // Read Key Objects + masterKey = (KMMasterKey) seProvider.onRestore(element); + seProvider.onRestore(element); // pop computedHmacKey + preSharedKey = (KMPreSharedKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMDeviceUniqueKeyPair) seProvider.onRestore(element); + rkpMacKey = (KMRkpMacKey) seProvider.onRestore(element); + handleProvisionStatusUpgrade(oldDataTable, oldDataIndex); + } + + private void handleUpgrade(Element element, short oldVersion) { + // Read Primitives + provisionStatus = element.readShort(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_3) { + secureBootMode = element.readByte(); + } + // Read Objects + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + additionalCertChain = (byte[]) element.readObject(); + bcc = (byte[]) element.readObject(); + oemRootPublicKey = (byte[]) element.readObject(); + // Read Key Objects + masterKey = (KMMasterKey) seProvider.onRestore(element); + preSharedKey = (KMPreSharedKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMDeviceUniqueKeyPair) seProvider.onRestore(element); + rkpMacKey = (KMRkpMacKey) seProvider.onRestore(element); + } + + public void getProvisionStatus(byte[] dataTable, byte[] scratchpad, short offset) { + Util.setShort(scratchpad, offset, (short) 0); + readDataEntry(dataTable, OLD_PROVISIONED_STATUS_OFFSET, scratchpad, offset); + } + + void handleProvisionStatusUpgrade(byte[] dataTable, short dataTableIndex) { + short dInex = repository.allocReclaimableMemory((short) 2); + byte data[] = repository.getHeap(); + getProvisionStatus(dataTable, data, dInex); + short pStatus = (short) (data[dInex] & 0x00ff); + if (KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED + == (pStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + pStatus |= + KMKeymasterApplet.PROVISION_STATUS_SE_LOCKED + | KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + } + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 1.0 to 3.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus = pStatus; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + repository.reclaimMemory((short) 2); + // Request object deletion for unreferenced address for additionalCertChain + JCSystem.requestObjectDeletion(); + } + + @Override + public short getBackupPrimitiveByteCount() { + // provisionStatus - 2 bytes + // secureBootMode - 1 byte + return (short) + (3 + + seProvider.getBackupPrimitiveByteCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } + + @Override + public short getBackupObjectCount() { + // AttestationIds - 8 + // AdditionalCertificateChain - 1 + // BCC - 1 + // oemRootPublicKey - 1 + return (short) + (11 + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupObjectCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java new file mode 100644 index 0000000..4583e02 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -0,0 +1,202 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMMap extends KMType { + + public static final short ANY_MAP_LENGTH = 0x1000; + private static final byte MAP_HEADER_SIZE = 4; + private static KMMap prototype; + + private KMMap() {} + + private static KMMap proto(short ptr) { + if (prototype == null) { + prototype = new KMMap(); + } + instanceTable[KM_MAP_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(MAP_TYPE, (short) MAP_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_MAP_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(MAP_TYPE, (short) (MAP_HEADER_SIZE + (length * 4))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMMap cast(short ptr) { + if (heap[ptr] != MAP_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short keyPtr, short valPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short keyIndex = + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4)); + Util.setShort(heap, keyIndex, keyPtr); + Util.setShort(heap, (short) (keyIndex + 2), valPtr); + } + + public short getKey(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4))); + } + + public short getKeyValue(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4 + 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + // Swap keys + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4)), + indexPtr1); + + // Swap Values + indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2))); + indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2)), + indexPtr1); + } + + public void canonicalize() { + KMCoseMap.canonicalize(instanceTable[KM_MAP_OFFSET], length()); + } + + public short containedType() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + MAP_HEADER_SIZE); + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public byte[] getBuffer() { + return heap; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java new file mode 100644 index 0000000..6246f21 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -0,0 +1,130 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMNInteger extends KMInteger { + + public static final byte SIGNED_MASK = (byte) 0x80; + private static KMNInteger prototype; + + private KMNInteger() {} + + private static KMNInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMNInteger(); + } + instanceTable[KM_NEG_INTEGER_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + return KMType.exp(NEG_INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = KMInteger.UINT_64; + } else { + length = KMInteger.UINT_32; + } + return KMType.instance(NEG_INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMNInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != NEG_INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_64); + return ptr; + } + + public static boolean isSignedInteger(byte[] num, short offset) { + byte val = num[offset]; + return SIGNED_MASK == (val & SIGNED_MASK); + } + + @Override + protected short getBaseOffset() { + return instanceTable[KM_NEG_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java new file mode 100644 index 0000000..2a53acd --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -0,0 +1,354 @@ +/* + * 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.KMException; +import com.android.javacard.seprovider.KMOperation; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMOperationState is the container of an active operation started by beginOperation function. This + * operation state is persisted by the applet in non volatile memory. However, this state is not + * retained if applet is upgraded. There will be four operation state records maintained i.e. only + * four active operations are supported at any given time. + */ +public class KMOperationState { + + // sizes + public static final byte OPERATION_HANDLE_SIZE = 8; + public static final byte DATA_SIZE = 11; + public static final byte AUTH_TIME_SIZE = 8; + // Secure user ids 5 * 8 = 40 bytes ( Considering Maximum 5 SECURE USER IDs) + // First two bytes are reserved to store number of secure ids. So total 42 bytes. + public static final byte USER_SECURE_IDS_SIZE = 42; + // byte type + private static final byte ALG = 0; + private static final byte PURPOSE = 1; + private static final byte PADDING = 2; + private static final byte BLOCK_MODE = 3; + private static final byte DIGEST = 4; + private static final byte FLAGS = 5; + private static final byte KEY_SIZE = 6; + private static final byte MAC_LENGTH = 7; + private static final byte MGF_DIGEST = 8; + private static final byte AUTH_TYPE = 9; + private static final byte MIN_MAC_LENGTH = 10; + private static final byte OPERATION = 0; + private static final byte HMAC_SIGNER_OPERATION = 1; + // Flag masks + private static final byte AUTH_PER_OP_REQD = 1; + private static final byte SECURE_USER_ID_REQD = 2; + private static final byte AUTH_TIMEOUT_VALIDATED = 4; + private static final byte AES_GCM_UPDATE_ALLOWED = 8; + private static final byte PROCESSED_INPUT_MSG = 16; + // Max user secure ids. + private static final byte MAX_SECURE_USER_IDS = 5; + + // Object References + private byte[] opHandle; + private byte[] authTime; + private byte[] userSecureIds; + private short[] data; + private Object[] operations; + + public KMOperationState() { + opHandle = JCSystem.makeTransientByteArray(OPERATION_HANDLE_SIZE, JCSystem.CLEAR_ON_RESET); + authTime = JCSystem.makeTransientByteArray(AUTH_TIME_SIZE, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientShortArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operations = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + userSecureIds = JCSystem.makeTransientByteArray(USER_SECURE_IDS_SIZE, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public void reset() { + byte index = 0; + while (index < DATA_SIZE) { + data[index] = KMType.INVALID_VALUE; + index++; + } + Util.arrayFillNonAtomic(opHandle, (short) 0, OPERATION_HANDLE_SIZE, (byte) 0); + Util.arrayFillNonAtomic(authTime, (short) 0, AUTH_TIME_SIZE, (byte) 0); + + if (null != operations[OPERATION]) { + ((KMOperation) operations[OPERATION]).abort(); + } + operations[OPERATION] = null; + + if (null != operations[HMAC_SIGNER_OPERATION]) { + ((KMOperation) operations[HMAC_SIGNER_OPERATION]).abort(); + } + operations[HMAC_SIGNER_OPERATION] = null; + } + + public short compare(byte[] handle, short start, short len) { + return Util.arrayCompare(handle, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getKeySize() { + return data[KEY_SIZE]; + } + + public void setKeySize(short keySize) { + data[KEY_SIZE] = keySize; + } + + public short getHandle() { + return KMInteger.uint_64(opHandle, (short) 0); + } + + public void setHandle(byte[] buf, short start, short len) { + Util.arrayCopyNonAtomic(buf, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getPurpose() { + return data[PURPOSE]; + } + + public void setPurpose(short purpose) { + data[PURPOSE] = purpose; + } + + public boolean isInputMsgProcessed() { + return (data[FLAGS] & PROCESSED_INPUT_MSG) != 0; + } + + public KMOperation getOperation() { + return (KMOperation) operations[OPERATION]; + } + + public void setOperation(KMOperation op) { + operations[OPERATION] = op; + } + + public boolean isAuthPerOperationReqd() { + return (data[FLAGS] & AUTH_PER_OP_REQD) != 0; + } + + public void setAuthPerOperationReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | AUTH_PER_OP_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~AUTH_PER_OP_REQD)); + } + } + + public boolean isAuthTimeoutValidated() { + return (data[FLAGS] & AUTH_TIMEOUT_VALIDATED) != 0; + } + + public void setAuthTimeoutValidated(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | AUTH_TIMEOUT_VALIDATED); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~AUTH_TIMEOUT_VALIDATED)); + } + } + + public boolean isSecureUserIdReqd() { + return (data[FLAGS] & SECURE_USER_ID_REQD) != 0; + } + + public short getAuthTime() { + return KMInteger.uint_64(authTime, (short) 0); + } + + public void setAuthTime(byte[] timeBuf, short start) { + Util.arrayCopyNonAtomic(timeBuf, start, authTime, (short) 0, AUTH_TIME_SIZE); + } + + public void setProcessedInputMsg(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | PROCESSED_INPUT_MSG); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~PROCESSED_INPUT_MSG)); + } + } + + public void setOneTimeAuthReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | SECURE_USER_ID_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~SECURE_USER_ID_REQD)); + } + } + + public short getAuthType() { + return data[AUTH_TYPE]; + } + + public void setAuthType(byte authType) { + data[AUTH_TYPE] = authType; + } + + public short getUserSecureId() { + short offset = 0; + short length = Util.getShort(userSecureIds, offset); + offset += 2; + if (length == 0) { + return KMType.INVALID_VALUE; + } + short arrObj = KMArray.instance(length); + short index = 0; + short obj; + while (index < length) { + obj = KMInteger.instance(userSecureIds, (short) (offset + index * 8), (short) 8); + KMArray.cast(arrObj).add(index, obj); + index++; + } + return KMIntegerArrayTag.instance(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, arrObj); + } + + public void setUserSecureId(short integerArrayPtr) { + short length = KMIntegerArrayTag.cast(integerArrayPtr).length(); + if (length > MAX_SECURE_USER_IDS) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + Util.arrayFillNonAtomic(userSecureIds, (short) 0, USER_SECURE_IDS_SIZE, (byte) 0); + short index = 0; + short obj; + short offset = 0; + offset = Util.setShort(userSecureIds, offset, length); + while (index < length) { + obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + Util.arrayCopyNonAtomic( + KMInteger.cast(obj).getBuffer(), + KMInteger.cast(obj).getStartOff(), + userSecureIds, + (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), + KMInteger.cast(obj).length()); + index++; + } + } + + public short getAlgorithm() { + return data[ALG]; + } + + public void setAlgorithm(short algorithm) { + data[ALG] = algorithm; + } + + public short getPadding() { + return data[PADDING]; + } + + public void setPadding(short padding) { + data[PADDING] = padding; + } + + public short getBlockMode() { + return data[BLOCK_MODE]; + } + + public void setBlockMode(short blockMode) { + data[BLOCK_MODE] = blockMode; + } + + public short getDigest() { + return data[DIGEST]; + } + + public void setDigest(byte digest) { + data[DIGEST] = digest; + } + + public short getMgfDigest() { + return data[MGF_DIGEST]; + } + + public void setMgfDigest(byte mgfDigest) { + data[MGF_DIGEST] = mgfDigest; + } + + public boolean isAesGcmUpdateAllowed() { + return (data[FLAGS] & AES_GCM_UPDATE_ALLOWED) != 0; + } + + public void setAesGcmUpdateComplete() { + data[FLAGS] = (byte) (data[FLAGS] & (~AES_GCM_UPDATE_ALLOWED)); + } + + public void setAesGcmUpdateStart() { + data[FLAGS] = (byte) (data[FLAGS] | AES_GCM_UPDATE_ALLOWED); + } + + public short getMinMacLength() { + return data[MIN_MAC_LENGTH]; + } + + public void setMinMacLength(short length) { + data[MIN_MAC_LENGTH] = length; + } + + public short getMacLength() { + return data[MAC_LENGTH]; + } + + public void setMacLength(short length) { + data[MAC_LENGTH] = length; + } + + public byte getBufferingMode() { + short alg = getAlgorithm(); + short purpose = getPurpose(); + short digest = getDigest(); + short padding = getPadding(); + short blockMode = getBlockMode(); + + if (alg == KMType.RSA + && ((digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) + || purpose == KMType.DECRYPT)) { + return KMType.BUF_RSA_DECRYPT_OR_NO_DIGEST; + } + + if (alg == KMType.EC && digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) { + return KMType.BUF_EC_NO_DIGEST; + } + + switch (alg) { + case KMType.AES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && blockMode == KMType.GCM) { + return KMType.BUF_AES_GCM_DECRYPT_BLOCK_ALIGN; + } + break; + case KMType.DES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN; + } + } + return KMType.BUF_NONE; + } + + public KMOperation getTrustedConfirmationSigner() { + return (KMOperation) operations[HMAC_SIGNER_OPERATION]; + } + + public void setTrustedConfirmationSigner(KMOperation hmacSignerOp) { + operations[HMAC_SIGNER_OPERATION] = hmacSignerOp; + } + + public boolean isTrustedConfirmationRequired() { + return operations[HMAC_SIGNER_OPERATION] != null; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java new file mode 100644 index 0000000..dec52eb --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -0,0 +1,127 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMRepository class manages volatile memory usage by the applet. Note the repository is only used + * by applet and it is not intended to be used by seProvider. + */ +public class KMRepository { + + public static final short HEAP_SIZE = 10000; + private static short[] reclaimIndex; + // Singleton instance + private static KMRepository repository; + // Class Attributes + private byte[] heap; + private short[] heapIndex; + + public KMRepository(boolean isUpgrading) { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + heapIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex[0] = HEAP_SIZE; + repository = this; + } + + public static KMRepository instance() { + return repository; + } + + public void onUninstall() { + // Javacard Runtime environment cleans up the data. + + } + + public void onProcess() {} + + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, HEAP_SIZE, (byte) 0); + heapIndex[0] = 0; + reclaimIndex[0] = HEAP_SIZE; + } + + public void onDeselect() {} + + public void onSelect() { + // If write through caching is implemented then this method will restore the data into cache + } + + // This function uses memory from the back of the heap(transient memory). Call + // reclaimMemory function immediately after the use. + public short allocReclaimableMemory(short length) { + if ((((short) (reclaimIndex[0] - length)) <= heapIndex[0]) || (length >= HEAP_SIZE / 2)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + reclaimIndex[0] -= length; + return reclaimIndex[0]; + } + + // Reclaims the memory back. + public void reclaimMemory(short length) { + if (reclaimIndex[0] < heapIndex[0]) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, reclaimIndex[0], length, (byte) 0); + reclaimIndex[0] += length; + } + + public short allocAvailableMemory() { + if (heapIndex[0] >= heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short index = heapIndex[0]; + heapIndex[0] = reclaimIndex[0]; + return index; + } + + public short alloc(short length) { + if ((((short) (heapIndex[0] + length)) > heap.length) + || (((short) (heapIndex[0] + length)) > reclaimIndex[0])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + heapIndex[0] += length; + return (short) (heapIndex[0] - length); + } + + public byte[] getHeap() { + return heap; + } + + public short getHeapIndex() { + return heapIndex[0]; + } + + // Use this function to reset the heapIndex to its previous state. + // Some of the data might be lost so use it carefully. + public void setHeapIndex(short offset) { + if (offset > heapIndex[0] || offset < 0) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, offset, (short) (heapIndex[0] - offset), (byte) 0); + heapIndex[0] = offset; + } + + public short getHeapReclaimIndex() { + return reclaimIndex[0]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java new file mode 100644 index 0000000..6165c31 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java @@ -0,0 +1,75 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMSemanticTag extends KMType { + + public static final short COSE_MAC_SEMANTIC_TAG = (short) 0x0011; + public static final short ROT_SEMANTIC_TAG = (short) 0x9C41; + private static KMSemanticTag prototype; + + private KMSemanticTag() {} + + private static KMSemanticTag proto(short ptr) { + if (prototype == null) { + prototype = new KMSemanticTag(); + } + instanceTable[KM_SEMANTIC_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp(short valuePtr) { + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMInteger.exp()); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMSemanticTag cast(short ptr) { + if (heap[ptr] != SEMANTIC_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short tag, short value) { + if (!isSemanticTagSupported(tag)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // The maximum tag size can be UINT32. Currently, we support + // only two tags which are short. + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), tag); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), value); + return ptr; + } + + private static boolean isSemanticTagSupported(short tag) { + tag = KMInteger.cast(tag).getShort(); + switch (tag) { + case COSE_MAC_SEMANTIC_TAG: + case ROT_SEMANTIC_TAG: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + 1)); + } + + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java new file mode 100644 index 0000000..14b7bb8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -0,0 +1,67 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMSimpleValue extends KMType { + + public static final byte FALSE = (byte) 20; + public static final byte TRUE = (byte) 21; + public static final byte NULL = (byte) 22; + private static KMSimpleValue prototype; + + private KMSimpleValue() {} + + private static KMSimpleValue proto(short ptr) { + if (prototype == null) { + prototype = new KMSimpleValue(); + } + instanceTable[KM_SIMPLE_VALUE_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(SIMPLE_VALUE_TYPE); + } + + public static KMSimpleValue cast(short ptr) { + if (heap[ptr] != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (!isSimpleValueValid(heap[(short) (ptr + 3)])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(byte value) { + if (!isSimpleValueValid(value)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(SIMPLE_VALUE_TYPE, (short) 1); + heap[(short) (ptr + 3)] = value; + return ptr; + } + + private static boolean isSimpleValueValid(byte value) { + switch (value) { + case TRUE: + case FALSE: + case NULL: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); + } + + public byte getValue() { + return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java new file mode 100644 index 0000000..3033a70 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java @@ -0,0 +1,102 @@ +/* + * 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.KMException; +import javacard.framework.Util; + +/** + * This class represents a tag as defined by keymaster hal specifications. It is composed of key + * value pair. The key consists of short tag type e.g. KMType.ENUM and short tag key e.g. + * KMType.ALGORITHM. The key is encoded as uint CBOR type with 4 bytes. This is followed by value + * which can be any CBOR type based on key. struct{byte tag=KMType.TAG_TYPE, short length, value) + * where value is subtype of KMTag i.e. struct{short tagType=one of tag types declared in KMType , + * short tagKey=one of the tag keys declared in KMType, value} where value is one of the sub-types + * of KMType. + */ +public class KMTag extends KMType { + + public static short getTagType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + public static short getKey(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2)); + } + + public static void assertPresence(short params, short tagType, short tagKey, short error) { + if (!isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static void assertAbsence(short params, short tagType, short tagKey, short error) { + if (isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static boolean isPresent(short params, short tagType, short tagKey) { + short tag = KMKeyParameters.findTag(tagType, tagKey, params); + return tag != KMType.INVALID_VALUE; + } + + public static boolean isEqual(short params, short tagType, short tagKey, short value) { + switch (tagType) { + case KMType.ENUM_TAG: + return KMEnumTag.getValue(tagKey, params) == value; + case KMType.UINT_TAG: + case KMType.DATE_TAG: + case KMType.ULONG_TAG: + return KMIntegerTag.isEqual(params, tagType, tagKey, value); + case KMType.ENUM_ARRAY_TAG: + return KMEnumArrayTag.contains(tagKey, value, params); + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + return KMIntegerArrayTag.contains(tagKey, value, params); + } + return false; + } + + public static void assertTrue(boolean condition, short error) { + if (!condition) { + KMException.throwIt(error); + } + } + + public static boolean isValidPublicExponent(short params) { + short pubExp = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, params); + if (pubExp == KMType.INVALID_VALUE) { + return false; + } + pubExp = KMIntegerTag.cast(pubExp).getValue(); + if (!(KMInteger.cast(pubExp).getShort() == 0x01 + && KMInteger.cast(pubExp).getSignificantShort() == 0x01)) { + return false; + } + return true; + } + + public static boolean isValidKeySize(short params) { + short keysize = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, params); + if (keysize == KMType.INVALID_VALUE) { + return false; + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + return KMIntegerTag.cast(keysize).isValidKeySize((byte) alg); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java new file mode 100644 index 0000000..80aebd2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java @@ -0,0 +1,80 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMTextString represents contiguous block of bytes. It corresponds to CBOR type of Text String. It + * extends KMByteBlob by specifying value field as zero or more sequence of bytes. struct{ byte + * TEXT_STR_TYPE; short length; sequence of bytes} + */ +public class KMTextString extends KMByteBlob { + + private static byte OFFSET_SIZE = 2; + + private static KMTextString prototype; + + private KMTextString() {} + + private static KMTextString proto(short ptr) { + if (prototype == null) { + prototype = new KMTextString(); + } + instanceTable[KM_TEXT_STRING_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(TEXT_STRING_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(TEXT_STRING_TYPE, (short) (length + OFFSET_SIZE)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMTextString + public static KMTextString cast(short ptr) { + if (heap[ptr] != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + protected short getBaseOffset() { + return instanceTable[KM_TEXT_STRING_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java new file mode 100644 index 0000000..a18cb7f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java @@ -0,0 +1,407 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + // Types + public static final byte BYTE_BLOB_TYPE = 0x01; + public static final byte INTEGER_TYPE = 0x02; + public static final byte ENUM_TYPE = 0x03; + public static final byte TAG_TYPE = 0x04; + public static final byte ARRAY_TYPE = 0x05; + public static final byte KEY_PARAM_TYPE = 0x06; + public static final byte KEY_CHAR_TYPE = 0x07; + public static final byte HW_AUTH_TOKEN_TYPE = 0x08; + public static final byte VERIFICATION_TOKEN_TYPE = 0x09; + public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; + public static final byte X509_CERT = 0x0B; + public static final byte NEG_INTEGER_TYPE = 0x0C; + public static final byte TEXT_STRING_TYPE = 0x0D; + public static final byte MAP_TYPE = 0x0E; + public static final byte COSE_KEY_TYPE = 0x0F; + public static final byte COSE_PAIR_TAG_TYPE = 0x10; + public static final byte COSE_PAIR_INT_TAG_TYPE = 0x20; + public static final byte COSE_PAIR_NEG_INT_TAG_TYPE = 0x30; + public static final byte COSE_PAIR_BYTE_BLOB_TAG_TYPE = 0x40; + public static final byte COSE_PAIR_COSE_KEY_TAG_TYPE = 0x60; + public static final byte COSE_PAIR_SIMPLE_VALUE_TAG_TYPE = 0x70; + public static final byte COSE_PAIR_TEXT_STR_TAG_TYPE = (byte) 0x80; + public static final byte SIMPLE_VALUE_TYPE = (byte) 0x90; + public static final byte COSE_HEADERS_TYPE = (byte) 0xA0; + public static final byte COSE_CERT_PAYLOAD_TYPE = (byte) 0xB0; + public static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + // Tag Types + public static final short INVALID_TAG = 0x0000; + public static final short ENUM_TAG = 0x1000; + public static final short ENUM_ARRAY_TAG = 0x2000; + public static final short UINT_TAG = 0x3000; + public static final short UINT_ARRAY_TAG = 0x4000; + public static final short ULONG_TAG = 0x5000; + public static final short DATE_TAG = 0x6000; + public static final short BOOL_TAG = 0x7000; + public static final short BIGNUM_TAG = (short) 0x8000; + public static final short BYTES_TAG = (short) 0x9000; + public static final short ULONG_ARRAY_TAG = (short) 0xA000; + public static final short TAG_TYPE_MASK = (short) 0xF000; + + // Enum Tag + // Internal tags + public static final short RULE = 0x7FFF; + public static final byte IGNORE_INVALID_TAGS = 0x00; + public static final byte FAIL_ON_INVALID_TAGS = 0x01; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + public static final byte CURVE_25519 = 0x04; + + // KeyBlobUsageRequirements Enum Tag key and values. + public static final short BLOB_USAGE_REQ = 0x012D; + public static final byte STANDALONE = 0x00; + public static final byte REQUIRES_FILE_SYSTEM = 0x01; + + // HardwareAuthenticatorType Enum Tag key and values. + public static final short USER_AUTH_TYPE = 0x01F8; + public static final byte USER_AUTH_NONE = 0x00; + public static final byte PASSWORD = 0x01; + public static final byte FINGERPRINT = 0x02; + public static final byte BOTH = 0x03; + // have to be power of 2 + public static final byte ANY = (byte) 0xFF; + + // Origin Enum Tag key and values. + public static final short ORIGIN = 0x02BE; + public static final byte GENERATED = 0x00; + public static final byte DERIVED = 0x01; + public static final byte IMPORTED = 0x02; + public static final byte UNKNOWN = 0x03; + public static final byte SECURELY_IMPORTED = 0x04; + + // Hardware Type tag key and values + public static final short HARDWARE_TYPE = 0x0130; + public static final byte SOFTWARE = 0x00; + public static final byte TRUSTED_ENVIRONMENT = 0x01; + public static final byte STRONGBOX = 0x02; + + // No Tag + // Derivation Function - No Tag defined + public static final short KEY_DERIVATION_FUNCTION = (short) 0xF001; + public static final byte DERIVATION_NONE = 0x00; + public static final byte RFC5869_SHA256 = 0x01; + public static final byte ISO18033_2_KDF1_SHA1 = 0x02; + public static final byte ISO18033_2_KDF1_SHA256 = 0x03; + public static final byte ISO18033_2_KDF2_SHA1 = 0x04; + public static final byte ISO18033_2_KDF2_SHA256 = 0x05; + + // KeyFormat - No Tag defined. + public static final short KEY_FORMAT = (short) 0xF002; + public static final byte X509 = 0x00; + public static final byte PKCS8 = 0x01; + public static final byte RAW = 0x03; + + // Verified Boot State + public static final short VERIFIED_BOOT_STATE = (short) 0xF003; + public static final byte VERIFIED_BOOT = 0x00; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte UNVERIFIED_BOOT = 0x02; + public static final byte FAILED_BOOT = 0x03; + + // Device Locked + public static final short DEVICE_LOCKED = (short) 0xF006; + public static final byte DEVICE_LOCKED_TRUE = 0x01; + public static final byte DEVICE_LOCKED_FALSE = 0x00; + + // Enum Array Tag + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; + + // OAEP MGF Digests - only SHA-1 is supported in Javacard + public static final short RSA_OAEP_MGF_DIGEST = 0xCB; + + // Integer Tag - UINT, ULONG and DATE + // UINT tags + // Keysize + public static final short KEYSIZE = 0x0003; + // Min Mac Length + public static final short MIN_MAC_LENGTH = 0x0008; + // Min Seconds between OPS + public static final short MIN_SEC_BETWEEN_OPS = 0x0193; + // Max Uses per Boot + public static final short MAX_USES_PER_BOOT = 0x0194; + // UserId + public static final short USERID = 0x01F5; + // Auth Timeout + public static final short AUTH_TIMEOUT = 0x01F9; + // Auth Timeout in Milliseconds + public static final short AUTH_TIMEOUT_MILLIS = 0x7FFF; + // OS Version + public static final short OS_VERSION = 0x02C1; + // OS Patch Level + public static final short OS_PATCH_LEVEL = 0x02C2; + // Vendor Patch Level + public static final short VENDOR_PATCH_LEVEL = 0x02CE; + // Boot Patch Level + public static final short BOOT_PATCH_LEVEL = 0x02CF; + // Mac Length + public static final short MAC_LENGTH = 0x03EB; + // Usage Count Limit + public static final short USAGE_COUNT_LIMIT = 0x195; + + // ULONG tags + // RSA Public Exponent + public static final short RSA_PUBLIC_EXPONENT = 0x00C8; + + // DATE tags + public static final short ACTIVE_DATETIME = 0x0190; + public static final short ORIGINATION_EXPIRE_DATETIME = 0x0191; + public static final short USAGE_EXPIRE_DATETIME = 0x0192; + public static final short CREATION_DATETIME = 0x02BD; + ; + public static final short CERTIFICATE_NOT_BEFORE = 0x03F0; + public static final short CERTIFICATE_NOT_AFTER = 0x03F1; + // Integer Array Tags - ULONG_REP and UINT_REP. + // User Secure Id + public static final short USER_SECURE_ID = (short) 0x01F6; + + // Boolean Tag + // Caller Nonce + public static final short CALLER_NONCE = (short) 0x0007; + // Include Unique Id + public static final short INCLUDE_UNIQUE_ID = (short) 0x00CA; + // Bootloader Only + public static final short BOOTLOADER_ONLY = (short) 0x012E; + // Rollback Resistance + public static final short ROLLBACK_RESISTANCE = (short) 0x012F; + // No Auth Required + public static final short NO_AUTH_REQUIRED = (short) 0x01F7; + // Allow While On Body + public static final short ALLOW_WHILE_ON_BODY = (short) 0x01FA; + // Max Boot Level + public static final short MAX_BOOT_LEVEL = (short) 0x03F2; + // Trusted User Presence Required + public static final short TRUSTED_USER_PRESENCE_REQUIRED = (short) 0x01FB; + // Trusted Confirmation Required + public static final short TRUSTED_CONFIRMATION_REQUIRED = (short) 0x01FC; + // Unlocked Device Required + public static final short UNLOCKED_DEVICE_REQUIRED = (short) 0x01FD; + // Reset Since Id Rotation + public static final short RESET_SINCE_ID_ROTATION = (short) 0x03EC; + // Early boot ended. + public static final short EARLY_BOOT_ONLY = (short) 0x0131; + // Device unique attestation. + public static final short DEVICE_UNIQUE_ATTESTATION = (short) 0x02D0; + + // Byte Tag + // Application Id + public static final short APPLICATION_ID = (short) 0x0259; + // Application Data + public static final short APPLICATION_DATA = (short) 0x02BC; + // Root Of Trust + public static final short ROOT_OF_TRUST = (short) 0x02C0; + // Unique Id + public static final short UNIQUE_ID = (short) 0x02C3; + // Attestation Challenge + public static final short ATTESTATION_CHALLENGE = (short) 0x02C4; + // Attestation Application Id + public static final short ATTESTATION_APPLICATION_ID = (short) 0x02C5; + // Attestation Id Brand + public static final short ATTESTATION_ID_BRAND = (short) 0x02C6; + // Attestation Id Device + public static final short ATTESTATION_ID_DEVICE = (short) 0x02C7; + // Attestation Id Product + public static final short ATTESTATION_ID_PRODUCT = (short) 0x02C8; + // Attestation Id Serial + public static final short ATTESTATION_ID_SERIAL = (short) 0x02C9; + // Attestation Id IMEI + public static final short ATTESTATION_ID_IMEI = (short) 0x02CA; + // Attestation Id MEID + public static final short ATTESTATION_ID_MEID = (short) 0x02CB; + // Attestation Id Manufacturer + public static final short ATTESTATION_ID_MANUFACTURER = (short) 0x02CC; + // Attestation Id Model + public static final short ATTESTATION_ID_MODEL = (short) 0x02CD; + // Associated Data + public static final short ASSOCIATED_DATA = (short) 0x03E8; + // Nonce + public static final short NONCE = (short) 0x03E9; + // Confirmation Token + public static final short CONFIRMATION_TOKEN = (short) 0x03ED; + // Serial Number - this is a big num but in applet we handle it as byte blob + public static final short CERTIFICATE_SERIAL_NUM = (short) 0x03EE; + // Subject Name + public static final short CERTIFICATE_SUBJECT_NAME = (short) 0x03EF; + + public static final short LENGTH_FROM_PDU = (short) 0xFFFF; + + public static final byte NO_VALUE = (byte) 0xff; + // Support Curves for Eek Chain validation. + public static final byte RKP_CURVE_P256 = 1; + // Type offsets. + public static final byte KM_TYPE_BASE_OFFSET = 0; + public static final byte KM_ARRAY_OFFSET = KM_TYPE_BASE_OFFSET; + public static final byte KM_BOOL_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 1; + public static final byte KM_BYTE_BLOB_OFFSET = KM_TYPE_BASE_OFFSET + 2; + public static final byte KM_BYTE_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 3; + public static final byte KM_ENUM_OFFSET = KM_TYPE_BASE_OFFSET + 4; + public static final byte KM_ENUM_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 5; + public static final byte KM_ENUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 6; + public static final byte KM_HARDWARE_AUTH_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 7; + public static final byte KM_HMAC_SHARING_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 8; + public static final byte KM_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 9; + public static final byte KM_INTEGER_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 10; + public static final byte KM_INTEGER_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 11; + public static final byte KM_KEY_CHARACTERISTICS_OFFSET = KM_TYPE_BASE_OFFSET + 12; + public static final byte KM_KEY_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 13; + public static final byte KM_VERIFICATION_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 14; + public static final byte KM_NEG_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_TEXT_STRING_OFFSET = KM_TYPE_BASE_OFFSET + 16; + public static final byte KM_MAP_OFFSET = KM_TYPE_BASE_OFFSET + 17; + public static final byte KM_COSE_KEY_OFFSET = KM_TYPE_BASE_OFFSET + 18; + public static final byte KM_COSE_KEY_INT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 19; + public static final byte KM_COSE_KEY_NINT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 20; + public static final byte KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 21; + public static final byte KM_COSE_KEY_COSE_KEY_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 22; + public static final byte KM_COSE_KEY_SIMPLE_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 23; + public static final byte KM_SIMPLE_VALUE_OFFSET = KM_TYPE_BASE_OFFSET + 24; + public static final byte KM_COSE_HEADERS_OFFSET = KM_TYPE_BASE_OFFSET + 25; + public static final byte KM_COSE_KEY_TXT_STR_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 26; + public static final byte KM_COSE_CERT_PAYLOAD_OFFSET = KM_TYPE_BASE_OFFSET + 27; + public static final byte KM_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 28; + public static final byte KM_SEMANTIC_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 29; + + // Attestation types + public static final byte NO_CERT = 0; + public static final byte ATTESTATION_CERT = 1; + public static final byte SELF_SIGNED_CERT = 2; + public static final byte FAKE_CERT = 3; + // Buffering Mode + public static final byte BUF_NONE = 0; + public static final byte BUF_RSA_DECRYPT_OR_NO_DIGEST = 1; + public static final byte BUF_EC_NO_DIGEST = 2; + public static final byte BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN = 3; + public static final byte BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN = 4; + public static final byte BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN = 5; + public static final byte BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN = 6; + public static final byte BUF_AES_GCM_DECRYPT_BLOCK_ALIGN = 7; + + // MAX ApplicationID or Application Data size + public static final byte MAX_APP_ID_APP_DATA_SIZE = 64; + // Max attestation challenge size. + public static final short MAX_ATTESTATION_CHALLENGE_SIZE = 128; + // Max certificate serial size. + public static final byte MAX_CERTIFICATE_SERIAL_SIZE = 20; + // Attestation Application ID + public static final short MAX_ATTESTATION_APP_ID_SIZE = 1024; + // Instance table + public static final byte INSTANCE_TABLE_SIZE = 30; + protected static final byte TLV_HEADER_SIZE = 3; + protected static KMRepository repository; + protected static byte[] heap; + protected static short[] instanceTable; + + public static void initialize() { + instanceTable = JCSystem.makeTransientShortArray(INSTANCE_TABLE_SIZE, JCSystem.CLEAR_ON_RESET); + KMType.repository = KMRepository.instance(); + KMType.heap = repository.getHeap(); + } + + public static byte getType(short ptr) { + return heap[ptr]; + } + + public static short length(short ptr) { + return Util.getShort(heap, (short) (ptr + 1)); + } + + public static short getValue(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + protected static short instance(byte type, short length) { + if (length < 0) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = repository.alloc((short) (length + TLV_HEADER_SIZE)); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + protected static short exp(byte type) { + short ptr = repository.alloc(TLV_HEADER_SIZE); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), INVALID_VALUE); + return ptr; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java new file mode 100644 index 0000000..590e73a --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -0,0 +1,129 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMVerificationToken represents VerificationToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte type=VERIFICATION_TOKEN_TYPE; + * short length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following + * elements: {KMInteger Challenge; KMInteger Timestamp; KMByteBlob PARAMETERS_VERIFIED; + * SecurityLevel level; KMByteBlob Mac}. + */ +public class KMVerificationToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte TIMESTAMP = 0x01; + public static final byte MAC = 0x02; + + private static KMVerificationToken prototype; + + private KMVerificationToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMVerificationToken proto(short ptr) { + if (prototype == null) { + prototype = new KMVerificationToken(); + } + KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMVerificationToken cast(short ptr) { + if (heap[ptr] != VERIFICATION_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java new file mode 100644 index 0000000..0b94432 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java @@ -0,0 +1,1591 @@ +/* + * 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.KMDeviceUniqueKeyPair; +import com.android.javacard.seprovider.KMException; +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 the remote key provisioning. Generates an RKP key and generates a certificate signing + * request(CSR). The generation of CSR is divided amoung 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 N times, where + * N is the number of keys. Similarly getResponse is called is multiple times till the client receives the + * response completely. + */ +public class RemotelyProvisionedComponentDevice { + + // Device Info labels + public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; + public static final byte[] MANUFACTURER = { + 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72 + }; + public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65}; + public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + public static final byte[] BOOTLOADER_STATE = { + 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65 + }; + public static final byte[] VB_META_DIGEST = { + 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74 + }; + public static final byte[] OS_VERSION = { + 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E + }; + 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 + }; + public static final byte[] BOOT_PATCH_LEVEL = { + 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + 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 + }; + public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + public static final byte[] SECURITY_LEVEL = { + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64}; + // Verified boot state values + public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; + public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; + // Boot loader state values + public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // Device info CDDL schema version + public static final byte DI_SCHEMA_VERSION = 2; + public static final byte[] DI_SECURITY_LEVEL = { + 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78 + }; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + private static final byte TRUE = 0x01; + private static final byte FALSE = 0x00; + // RKP Version + private static final short RKP_VERSION = (short) 0x02; + // Boot params + 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; + private static final boolean IS_ACC_SUPPORTED_IN_RKP_SERVER = false; + private static final short MAX_SEND_DATA = 512; + private static final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + private static final byte[] uniqueId = { + 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, 0x20, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, + 0x74 + }; // "strongbox keymint" + // more data or no data + private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve + private static final byte NO_DATA = 0x00; + // Response processing states + private static final byte START_PROCESSING = 0x00; + private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; + private static final byte PROCESSING_BCC_COMPLETE = 0x04; + private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; // Additional certificate chain. + private static final byte PROCESSING_ACC_COMPLETE = 0x0A; + // data table + private static final short DATA_SIZE = 512; + private static final byte DATA_INDEX_SIZE = 11; + // data offsets + private static final byte EPHEMERAL_MAC_KEY = 0; + private static final byte TOTAL_KEYS_TO_SIGN = 1; + private static final byte KEYS_TO_SIGN_COUNT = 2; + private static final byte TEST_MODE = 3; + private static final byte EEK_KEY = 4; + private static final byte EEK_KEY_ID = 5; + private static final byte CHALLENGE = 6; + private static final byte GENERATE_CSR_PHASE = 7; + private static final byte EPHEMERAL_PUB_KEY = 8; + private static final byte RESPONSE_PROCESSING_STATE = 9; + private static final byte ACC_PROCESSED_LENGTH = 10; + + // data item sizes + private static final byte MAC_KEY_SIZE = 32; + private static final byte SHORT_SIZE = 2; + private static final byte BYTE_SIZE = 1; + private static final byte TEST_MODE_SIZE = 1; + // generate csr states + private static final byte BEGIN = 0x01; + private static final byte UPDATE = 0x02; + private static final byte FINISH = 0x04; + private static final byte GET_RESPONSE = 0x06; + + // RKP mac key size + private static final byte RKP_MAC_KEY_SIZE = 32; + public static Object[] authorizedEekRoots; + public short[] rkpTmpVariables; + // variables + private byte[] data; + private KMEncoder encoder; + private KMDecoder decoder; + private KMRepository repository; + private KMSEProvider seProvider; + private KMKeymintDataStore storeDataInst; + private Object[] operation; + private short[] dataIndex; + + public RemotelyProvisionedComponentDevice( + 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; + createAuthorizedEEKRoot(); + } + + private void createAuthorizedEEKRoot() { + if (authorizedEekRoots == null) { + authorizedEekRoots = + new Object[] { + new byte[] { + (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, + (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, + (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, + (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, + (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, + (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, + (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, + (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, + (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, + (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, + (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f + }, + }; + } + } + + 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 short getEntryLength(short index) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(data, index); + } + + private void processGetRkpHwInfoCmd(APDU apdu) { + // Make the response + // Author name - Google. + short respPtr = KMArray.instance((short) 5); + 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(google, (short) 0, (short) google.length)); + resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_P256)); + resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); + 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 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, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Generate ephemeral mac key. + short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, MAC_KEY_SIZE); + seProvider.newRandomNumber(data, dataEntryIndex, MAC_KEY_SIZE); + // Initialize hmac operation. + initHmacOperation(); + // Partially encode CoseMac structure with partial payload. + constructPartialPubKeysToSignMac( + scratchPad, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), + KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort()); + // Store the total keys in data table. + dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); + Util.setShort( + data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); + // Store the test mode value in data table. + dataEntryIndex = createEntry(TEST_MODE, TEST_MODE_SIZE); + data[dataEntryIndex] = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 2)).getValue()) + ? TRUE + : FALSE; + // Store the current csr status, which is BEGIN. + createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); + updateState(BEGIN); + // Send response. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + 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); + 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 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 Hmac update with input as encoded CoseKey. + ((KMOperation) operation[0]).update(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. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateEekChain(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + 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 arrSignPtr = KMArray.exp(arrInst); + arrInst = KMKeymasterApplet.receiveIncoming(apdu, arrSignPtr); + if (KMArray.cast(arrInst).length() == 0) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Validate eek chain. + short eekKey = validateAndExtractEekPub(arrInst, scratchPad); + // Store eek public key and eek id in the data table. + short eekKeyId = KMCoseKey.cast(eekKey).getKeyIdentifier(); + short dataEntryIndex = createEntry(EEK_KEY_ID, KMByteBlob.cast(eekKeyId).length()); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(eekKeyId).getBuffer(), + KMByteBlob.cast(eekKeyId).getStartOff(), + data, + dataEntryIndex, + KMByteBlob.cast(eekKeyId).length()); + // Convert the coseKey to a public key. + short len = KMCoseKey.cast(eekKey).getEcdsa256PublicKey(scratchPad, (short) 0); + dataEntryIndex = createEntry(EEK_KEY, len); + Util.arrayCopyNonAtomic(scratchPad, (short) 0, data, dataEntryIndex, len); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateChallenge(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMByteBlob.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Store the challenge in the data table. + short challenge = KMArray.cast(arr).get((short) 0); + short challengeLen = KMByteBlob.cast(challenge).length(); + if (challengeLen > 64) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short dataEntryIndex = createEntry(CHALLENGE, challengeLen); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(challenge).getBuffer(), + KMByteBlob.cast(challenge).getStartOff(), + data, + dataEntryIndex, + challengeLen); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + // This function returns pubKeysToSignMac, deviceInfo and partially constructed protected data + // wrapped inside byte blob. The partial protected data contains Headers and encrypted signedMac. + 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); + // release operation + releaseOperation(); + short pubKeysToSignMac = KMByteBlob.instance(scratchPad, (short) 0, len); + // Create DeviceInfo + short deviceInfo = createDeviceInfo(scratchPad); + // Generate Nonce for AES-GCM + seProvider.newRandomNumber(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + short nonce = + KMByteBlob.instance(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + // Initializes cipher instance. + initAesGcmOperation(scratchPad, nonce); + // Encode Enc_Structure as additional data for AES-GCM. + processAesGcmUpdateAad(scratchPad); + short partialPayloadLen = processSignedMac(scratchPad, pubKeysToSignMac, deviceInfo); + short partialCipherText = KMByteBlob.instance(scratchPad, (short) 0, partialPayloadLen); + short coseEncryptProtectedHeader = getCoseEncryptProtectedHeader(scratchPad); + short coseEncryptUnProtectedHeader = getCoseEncryptUnprotectedHeader(scratchPad, nonce); + len = + KMKeymasterApplet.encodeToApduBuffer( + deviceInfo, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short encodedDeviceInfo = KMByteBlob.instance(scratchPad, (short) 0, len); + updateState(FINISH); + short arr = KMArray.instance((short) 7); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, pubKeysToSignMac); + KMArray.cast(arr).add((short) 2, encodedDeviceInfo); + KMArray.cast(arr).add((short) 3, coseEncryptProtectedHeader); + KMArray.cast(arr).add((short) 4, coseEncryptUnProtectedHeader); + KMArray.cast(arr).add((short) 5, partialCipherText); + KMArray.cast(arr).add((short) 6, KMInteger.uint_8(MORE_DATA)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processGetResponse(APDU apdu) throws Exception { + try { + // The prior state should be FINISH. + validateState((byte) (FINISH | GET_RESPONSE)); + byte[] scratchPad = apdu.getBuffer(); + short len = 0; + short recipientStructure = KMArray.instance((short) 0); + byte moreData = MORE_DATA; + byte state = getCurrentOutputProcessingState(); + switch (state) { + case START_PROCESSING: + case PROCESSING_BCC_IN_PROGRESS: + len = processBcc(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_BCC_COMPLETE: + case PROCESSING_ACC_IN_PROGRESS: + len = processAdditionalCertificateChain(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_ACC_COMPLETE: + recipientStructure = processRecipientStructure(scratchPad); + len = processFinalData(scratchPad); + moreData = NO_DATA; + releaseOperation(); + clearDataTable(); + break; + default: + KMException.throwIt(KMError.INVALID_STATE); + } + short data = KMByteBlob.instance(scratchPad, (short) 0, len); + short arr = KMArray.instance((short) 4); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, data); + KMArray.cast(arr).add((short) 2, recipientStructure); + // represents there is more output to retrieve + KMArray.cast(arr).add((short) 3, KMInteger.uint_8(moreData)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + 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_UPDATE_EEK_CHAIN_CMD: + processUpdateEekChain(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_CHALLENGE_CMD: + processUpdateChallenge(apdu); + break; + case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: + processFinishSendData(apdu); + break; + case KMKeymasterApplet.INS_GET_RESPONSE_CMD: + processGetResponse(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } + + private boolean isAdditionalCertificateChainPresent() { + if (!IS_ACC_SUPPORTED_IN_RKP_SERVER || (TRUE == data[getEntry(TEST_MODE)])) { + // Don't include AdditionalCertificateChain in ProtectedData if either + // 1. RKP server does not support processing of X.509 Additional Certificate Chain. + // 2. Requested CSR for test mode. + return false; + } + return (storeDataInst.getAdditionalCertChainLength() == 0 ? false : true); + } + + private short processFinalData(byte[] scratchPad) { + // Call finish on AES GCM Cipher + short empty = repository.alloc((short) 0); + short len = + ((KMOperation) operation[0]) + .finish(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); + return len; + } + + 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) { + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : 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, + KMType.INVALID_VALUE, + 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; + } + + /** + * This function validates the EEK Chain and extracts the leaf public key, which is used to + * generate shared secret using ECDH. + * + * @param eekArr EEK cert chain array pointer. + * @param scratchPad Scratch buffer used to store temp results. + * @return CoseKey instance. + */ + private short validateAndExtractEekPub(short eekArr, byte[] scratchPad) { + short leafPubKey = 0; + try { + leafPubKey = + KMKeymasterApplet.validateCertChain( + (TRUE == data[getEntry(TEST_MODE)]) ? false : true, // validate EEK root + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + eekArr, + scratchPad, + authorizedEekRoots); + } catch (KMException e) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + return leafPubKey; + } + + 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; + } + + /** + * This function constructs a Mac Structure, encode it and signs the encoded buffer with the + * ephemeral mac key. + */ + private void constructPartialPubKeysToSignMac( + byte[] scratchPad, short arrayLength, short encodedCoseKeysLen) { + short ptr; + short len; + 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 + ptr = + KMCose.constructCoseMacStructure( + protectedHeader, KMByteBlob.instance((short) 0), KMType.INVALID_VALUE); + // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 + len = + KMKeymasterApplet.encodeToApduBuffer( + ptr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct partial payload - Bstr Header + Array Header + // The maximum combined length of bstr header and array header length is 6 bytes. + // The lengths will never exceed Max SHORT value. + short arrPtr = KMArray.instance(arrayLength); + for (short i = 0; i < arrayLength; i++) { + KMArray.cast(arrPtr).add(i, KMType.INVALID_VALUE); + } + arrayLength = encoder.getEncodedLength(arrPtr); + short bufIndex = repository.alloc((short) 6); + short partialPayloadLen = + encoder.encodeByteBlobHeader( + (short) (arrayLength + encodedCoseKeysLen), repository.getHeap(), bufIndex, (short) 3); + + partialPayloadLen += + encoder.encode( + arrPtr, + repository.getHeap(), + (short) (bufIndex + partialPayloadLen), + repository.getHeapReclaimIndex()); + Util.arrayCopyNonAtomic(repository.getHeap(), bufIndex, scratchPad, len, partialPayloadLen); + ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); + } + + private short createSignedMac( + KMDeviceUniqueKeyPair deviceUniqueKeyPair, + byte[] scratchPad, + short deviceMapPtr, + short pubKeysToSign) { + // Challenge + short dataEntryIndex = getEntry(CHALLENGE); + short challengePtr = KMByteBlob.instance(data, dataEntryIndex, getEntryLength(CHALLENGE)); + // Ephemeral mac key + dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + short ephmeralMacKey = + KMByteBlob.instance(data, dataEntryIndex, getEntryLength(EPHEMERAL_MAC_KEY)); + + /* Prepare AAD */ + short aad = KMArray.instance((short) 3); + KMArray.cast(aad).add((short) 0, challengePtr); + KMArray.cast(aad).add((short) 1, deviceMapPtr); + KMArray.cast(aad).add((short) 2, pubKeysToSign); + aad = + KMKeymasterApplet.encodeToApduBuffer( + aad, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + aad = KMByteBlob.instance(scratchPad, (short) 0, aad); + + /* 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 cose sign structure */ + short signStructure = KMCose.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); + signStructure = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short len = + seProvider.ecSign256( + deviceUniqueKeyPair, scratchPad, (short) 0, signStructure, scratchPad, signStructure); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature( + KMByteBlob.instance(scratchPad, signStructure, len), scratchPad, signStructure); + signStructure = KMByteBlob.instance(scratchPad, signStructure, len); + + /* Construct unprotected headers */ + short unprotectedHeader = KMArray.instance((short) 0); + unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); + + /* construct Cose_Sign1 */ + return KMCose.constructCoseSign1( + protectedHeaders, unprotectedHeader, ephmeralMacKey, signStructure); + } + + private KMDeviceUniqueKeyPair createDeviceUniqueKeyPair(boolean testMode, byte[] scratchPad) { + KMDeviceUniqueKeyPair deviceUniqueKeyPair; + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + if (testMode) { + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + deviceUniqueKeyPair = + storeDataInst.createRkpTestDeviceUniqueKeyPair( + scratchPad, + (short) 128, + rkpTmpVariables[1], + scratchPad, + (short) 0, + rkpTmpVariables[0]); + } else { + deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair(false); + } + return deviceUniqueKeyPair; + } + + /** + * DeviceInfo is a CBOR Map structure described by the following CDDL. + * + *

DeviceInfo = { "brand" : tstr, "manufacturer" : tstr, "product" : tstr, "model" : tstr, + * "device" : tstr, "vb_state" : "green" / "yellow" / "orange", // Taken from the AVB values + * "bootloader_state" : "locked" / "unlocked", // Taken from the AVB values "vbmeta_digest": bstr, + * // Taken from the AVB values ? "os_version" : tstr, // Same as android.os.Build.VERSION.release + * "system_patch_level" : uint, // YYYYMMDD "boot_patch_level" : uint, //YYYYMMDD + * "vendor_patch_level" : uint, // YYYYMMDD "version" : 2, // TheCDDL schema version + * "security_level" : "tee" / "strongbox" "fused": 1 / 0, } + */ + 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, DEVICE_INFO_VERSION, KMInteger.uint_8(DI_SCHEMA_VERSION)); + 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; + } + // ---------------------------------------------------------------------------- + + // ---------------------------------------------------------------------------- + // ECDH HKDF + private short ecdhHkdfDeriveKey( + byte[] privKeyA, + short privKeyAOff, + short privKeyALen, + byte[] pubKeyA, + short pubKeyAOff, + short pubKeyALen, + byte[] pubKeyB, + short pubKeyBOff, + short pubKeyBLen, + byte[] scratchPad) { + short key = + seProvider.ecdhKeyAgreement( + privKeyA, + privKeyAOff, + privKeyALen, + pubKeyB, + pubKeyBOff, + pubKeyBLen, + scratchPad, + (short) 0); + key = KMByteBlob.instance(scratchPad, (short) 0, key); + + // ignore 0x04 for ephemerical public key as kdfContext should not include 0x04. + pubKeyAOff += 1; + pubKeyALen -= 1; + pubKeyBOff += 1; + pubKeyBLen -= 1; + short kdfContext = + KMCose.constructKdfContext( + pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, true); + kdfContext = + KMKeymasterApplet.encodeToApduBuffer( + kdfContext, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + kdfContext = KMByteBlob.instance(scratchPad, (short) 0, kdfContext); + + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 32, (byte) 0); + seProvider.hkdf( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + (short) 32, + KMByteBlob.cast(kdfContext).getBuffer(), + KMByteBlob.cast(kdfContext).getStartOff(), + KMByteBlob.cast(kdfContext).length(), + scratchPad, + (short) 32, // offset + (short) 32 // Length of expected output. + ); + Util.arrayCopy(scratchPad, (short) 32, scratchPad, (short) 0, (short) 32); + return (short) 32; + } + + // ---------------------------------------------------------------------------- + // This function returns the instance of private key and It stores the public key in the + // data table for later usage. + private short generateEphemeralEcKey(byte[] scratchPad) { + // Generate ephemeral ec key. + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + // Copy the ephemeral private key from scratch pad + short ptr = KMByteBlob.instance(rkpTmpVariables[0]); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + rkpTmpVariables[0]); + // Store ephemeral public key in data table for later usage. + short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, rkpTmpVariables[1]); + Util.arrayCopyNonAtomic(scratchPad, (short) 128, data, dataEntryIndex, rkpTmpVariables[1]); + return ptr; + } + + private void initHmacOperation() { + short dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + operation[0] = + seProvider.getRkpOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) 0, + data, + dataEntryIndex, + getEntryLength(EPHEMERAL_MAC_KEY), + null, + (short) 0, + (short) 0, + (short) 0); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private void initAesGcmOperation(byte[] scratchPad, short nonce) { + // Generate Ephemeral mac key + short privKey = generateEphemeralEcKey(scratchPad); + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // Generate session key + short eekIndex = getEntry(EEK_KEY); + // Generate session key + short sessionKeyLen = + ecdhHkdfDeriveKey( + KMByteBlob.cast(privKey).getBuffer(), /* Ephemeral Private Key */ + KMByteBlob.cast(privKey).getStartOff(), + KMByteBlob.cast(privKey).length(), + data, /* Ephemeral Public key */ + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + data, /* EEK Public key */ + eekIndex, + getEntryLength(EEK_KEY), + scratchPad /* scratchpad */); + // Initialize the Cipher object. + operation[0] = + seProvider.getRkpOperation( + KMType.ENCRYPT, + KMType.AES, + (byte) 0, + KMType.PADDING_NONE, + KMType.GCM, + scratchPad, /* key */ + (short) 0, + sessionKeyLen, + KMByteBlob.cast(nonce).getBuffer(), /* nonce */ + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + (short) (KMKeymasterApplet.AES_GCM_AUTH_TAG_LENGTH * 8)); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private short processRecipientStructure(byte[] scratchPad) { + short protectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ECDH_ES_HKDF_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeaderRecipient = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaderRecipient, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaderRecipient = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaderRecipient); + + /* Construct unprotected headers */ + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // 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), + KMType.INVALID_VALUE, + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + data, + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + KMType.INVALID_VALUE, + false); + short keyIdentifierPtr = + KMByteBlob.instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); + short unprotectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, coseKey); + + // Construct recipients structure. + return KMCose.constructRecipientsStructure( + protectedHeaderRecipient, + unprotectedHeaderRecipient, + KMSimpleValue.instance(KMSimpleValue.NULL)); + } + + private short getAdditionalCertChainProcessedLength() { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(ACC_PROCESSED_LENGTH, SHORT_SIZE); + Util.setShort(data, dataEntryIndex, (short) 0); + return (short) 0; + } + return Util.getShort(data, dataEntryIndex); + } + + private void updateAdditionalCertChainProcessedLength(short processedLen) { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + Util.setShort(data, dataEntryIndex, processedLen); + } + + private short processAdditionalCertificateChain(byte[] scratchPad) { + byte[] persistedData = storeDataInst.getAdditionalCertChain(); + short totalAccLen = Util.getShort(persistedData, (short) 0); + if (totalAccLen == 0) { + // No Additional certificate chain present. + return 0; + } + short processedLen = getAdditionalCertChainProcessedLength(); + short lengthToSend = (short) (totalAccLen - processedLen); + if (lengthToSend > MAX_SEND_DATA) { + lengthToSend = MAX_SEND_DATA; + } + short cipherTextLen = + ((KMOperation) operation[0]) + .update(persistedData, (short) (2 + processedLen), lengthToSend, scratchPad, (short) 0); + processedLen += lengthToSend; + updateAdditionalCertChainProcessedLength(processedLen); + // Update the output processing state. + updateOutputProcessingState( + (processedLen == totalAccLen) ? PROCESSING_ACC_COMPLETE : PROCESSING_ACC_IN_PROGRESS); + return cipherTextLen; + } + + // BCC for STRONGBOX has chain length of 2. So it can be returned in a single go. + private short processBcc(byte[] scratchPad) { + // Construct BCC + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : false; + short len; + if (testMode) { + short bcc = KMKeymasterApplet.generateBcc(true, scratchPad); + len = + KMKeymasterApplet.encodeToApduBuffer( + bcc, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + } else { + byte[] bcc = storeDataInst.getBootCertificateChain(); + len = Util.getShort(bcc, (short) 0); + Util.arrayCopyNonAtomic(bcc, (short) 2, scratchPad, (short) 0, len); + } + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + // move cipher text on scratch pad from starting position. + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); + // If there is no additional certificate chain present then put the state to + // PROCESSING_ACC_COMPLETE. + updateOutputProcessingState( + isAdditionalCertificateChainPresent() ? PROCESSING_BCC_COMPLETE : PROCESSING_ACC_COMPLETE); + return cipherTextLen; + } + + // AAD is the CoseEncrypt structure + private void processAesGcmUpdateAad(byte[] scratchPad) { + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + short coseEncryptStr = + KMCose.constructCoseEncryptStructure(protectedHeader, KMByteBlob.instance((short) 0)); + coseEncryptStr = + KMKeymasterApplet.encodeToApduBuffer( + coseEncryptStr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + ((KMOperation) operation[0]).updateAAD(scratchPad, (short) 0, coseEncryptStr); + } + + private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short deviceInfo) { + // Construct SignedMac + KMDeviceUniqueKeyPair deviceUniqueKeyPair = + createDeviceUniqueKeyPair((TRUE == data[getEntry(TEST_MODE)]) ? true : false, scratchPad); + // Create signedMac + short signedMac = + createSignedMac(deviceUniqueKeyPair, scratchPad, deviceInfo, pubKeysToSignMac); + // Prepare partial data for encryption. + short arrLength = (short) (isAdditionalCertificateChainPresent() ? 3 : 2); + short arr = KMArray.instance(arrLength); + KMArray.cast(arr).add((short) 0, signedMac); + KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE); + if (arrLength == 3) { + KMArray.cast(arr).add((short) 2, KMType.INVALID_VALUE); + } + short len = + KMKeymasterApplet.encodeToApduBuffer( + arr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + return cipherTextLen; + } + + private short getCoseEncryptProtectedHeader(byte[] scratchPad) { + // CoseEncrypt protected headers. + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + return KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + } + + private short getCoseEncryptUnprotectedHeader(byte[] scratchPad, short nonce) { + /* CoseEncrypt unprotected headers */ + return KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); + } + + 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), + KMType.INVALID_VALUE, + 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(MAC_KEY_SIZE); + Util.arrayFillNonAtomic( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + MAC_KEY_SIZE, + (byte) 0); + result = + seProvider.hmacSign( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + MAC_KEY_SIZE, + data, + dataStart, + dataLength, + signature, + signatureStart); + } else { + result = + seProvider.hmacSign( + storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart); + } + return result; + } +} -- cgit v1.2.3 From 84fc8eafb41c06fa764f120ee39b27a57cce1de9 Mon Sep 17 00:00:00 2001 From: avinashhedage Date: Thu, 22 Dec 2022 05:30:27 +0000 Subject: keymint200: Addressed review comments provided on aosp/2082578 Bug: b/242702664 Test: run VtsAidlKeyMintTarget Change-Id: Idc9cb683f2a3a9f9625fe78b9ce4be66f7528322 --- .../javacard/keymaster/KMAndroidSEApplet.java | 2 +- .../javacard/keymaster/KMAttestationCertImpl.java | 17 +++- .../javacard/seprovider/KMAndroidSEProvider.java | 13 ++- .../android/javacard/seprovider/KMPoolManager.java | 102 ++++++++++----------- .../com/android/javacard/seprovider/KMType.java | 3 - .../android/javacard/keymaster/KMAsn1Parser.java | 37 +++++++- .../javacard/keymaster/KMKeymasterApplet.java | 34 ++++--- .../javacard/keymaster/KMKeymintDataStore.java | 2 +- .../RemotelyProvisionedComponentDevice.java | 46 ++++++---- 9 files changed, 162 insertions(+), 94 deletions(-) diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java index 27428ca..5a8eb76 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -31,7 +31,7 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis // Magic number version private static final byte KM_MAGIC_NUMBER = (byte) 0x82; // MSB byte is for Major version and LSB byte is for Minor version. - public static final short KM_APPLET_PACKAGE_VERSION = 0x0300; + public static final short KM_APPLET_PACKAGE_VERSION = 0x0301; private static final byte KM_BEGIN_STATE = 0x00; private static final byte ILLEGAL_STATE = KM_BEGIN_STATE + 1; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java index 90bc3fe..90ec7f2 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -61,8 +61,21 @@ public class KMAttestationCertImpl implements KMAttestationCert { // Signature algorithm identifier - sha256WithRSAEncryption - 1.2.840.113549.1.1.11 // SEQUENCE of alg OBJ ID and parameters = NULL. private static final byte[] X509RsaSignAlgIdentifier = { - 0x30, 0x0D, 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, - 0x0B, 0x05, 0x00 + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 }; // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java index a3198dd..3b13b3e 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -141,6 +141,9 @@ public class KMAndroidSEProvider implements KMSEProvider { public AESKey createAESKey(short keysize) { try { + if (keysize > TMP_ARRAY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); return createAESKey(tmpArray, (short) 0, (short) (keysize / 8)); } finally { @@ -157,6 +160,8 @@ public class KMAndroidSEProvider implements KMSEProvider { } else if (keysize == 256) { key = (AESKey) aesKeys[KEYSIZE_256_OFFSET]; key.setKey(buf, (short) startOff); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } return key; } @@ -176,6 +181,10 @@ public class KMAndroidSEProvider implements KMSEProvider { } public HMACKey createHMACKey(short keysize) { + // As per the KeyMint2.0 specification + // The minimum supported HMAC key size is 64 bits + // The maximum supported HMAC key size is 512 bits + // The keysize should be a multiple of 8. if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); } @@ -672,10 +681,10 @@ public class KMAndroidSEProvider implements KMSEProvider { case KMType.RSA_OAEP: { if (digest == KMType.SHA1) { - /* MGF Digest is SHA1 */ + /* MGF Digest is SHA1 */ return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1; } else if (digest == KMType.SHA2_256) { - /* MGF Digest is SHA256 */ + /* MGF Digest is SHA256 */ return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256; } else { KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java index fa32c70..d4d98e3 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -113,68 +113,68 @@ public class KMPoolManager { public static void initStatics() { secp256r1_P = - new byte[] { - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFF - }; + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF + }; secp256r1_A = - new byte[] { - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFC - }; + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFC + }; secp256r1_B = - new byte[] { - (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, - (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, - (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, - (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, - (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, - (byte) 0x60, (byte) 0x4B - }; + new byte[] { + (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, + (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, + (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, + (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, + (byte) 0x60, (byte) 0x4B + }; secp256r1_S = - new byte[] { - (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, - (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, - (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, - (byte) 0x7E, (byte) 0x90 - }; + new byte[] { + (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, + (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, + (byte) 0x7E, (byte) 0x90 + }; // Uncompressed form secp256r1_UCG = - new byte[] { - (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, - (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, - (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, - (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, - (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, - (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, - (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, - (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, - (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, - (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, - (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 - }; + new byte[] { + (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, + (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, + (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, + (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, + (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, + (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, + (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, + (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, + (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, + (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, + (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 + }; secp256r1_N = - new byte[] { - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, - (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, - (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, - (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, - (byte) 0x25, (byte) 0x51 - }; + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, + (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, + (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, + (byte) 0x25, (byte) 0x51 + }; } private KMPoolManager() { diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java index 648d323..82cbe26 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java @@ -16,7 +16,6 @@ package com.android.javacard.seprovider; - /** * This class declares all types, tag types, and tag keys. It also establishes basic structure of * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, @@ -77,6 +76,4 @@ public abstract class KMType { public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; public static final byte RSA_PKCS1_1_5_SIGN = 0x05; public static final byte PKCS7 = 0x40; - - } diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java index 65ca94f..8ff1bc3 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -25,13 +25,40 @@ public class KMAsn1Parser { 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 }; public static final byte[] RSA_ALGORITHM = { - 0x06,0x09,0x2A,(byte)0x86,0x48,(byte)0x86, - (byte)0xF7,0x0D,0x01,0x01,0x01,0x05,0x00 + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 }; public static final byte[] EC_ALGORITHM = { - 0x06,0x07,0x2a,(byte)0x86,0x48,(byte)0xce, - 0x3d,0x02,0x01,0x06,0x08,0x2a,(byte)0x86,0x48, - (byte)0xce,0x3d,0x03,0x01,0x07 + 0x06, + 0x07, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x02, + 0x01, + 0x06, + 0x08, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x03, + 0x01, + 0x07 }; public static final short MAX_EMAIL_ADD_LEN = 255; private static final byte DATA_START_OFFSET = 0; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index b616085..e196371 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -56,7 +56,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E }; // "KeymasterSharedMac" - public static final byte[] ckdfLable = { + public static final byte[] ckdfLabel = { 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, 0x61, 0x63 }; @@ -75,7 +75,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // which is used while creating mac for key paramters. public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K // Data Dictionary items - public static final byte DATA_ARRAY_SIZE = 40; public static final byte TMP_VARIABLE_ARRAY_SIZE = 5; public static final byte KEY_PARAMETERS = 0; public static final byte KEY_CHARACTERISTICS = 1; @@ -116,6 +115,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final byte CONFIRMATION_TOKEN = 36; public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37; public static final byte CUSTOM_TAGS = 38; + public static final byte DATA_ARRAY_SIZE = 39; // Keyblob offsets. public static final byte KEY_BLOB_VERSION_OFFSET = 0; public static final byte KEY_BLOB_SECRET = 1; @@ -176,7 +176,10 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe protected static final short MAX_KEY_CHARS_SIZE = 512; protected static final short MAX_KEYBLOB_SIZE = 1024; private static final short MAX_AUTH_DATA_SIZE = (short) 512; - private static final short DERIVE_KEY_INPUT_SIZE = (short) 256; + // The minimum bits in length for AES-GCM tag. + private static final short 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, @@ -260,6 +263,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe private static final byte INS_KM_VENDOR_END_CMD = (byte) 0xFF; // ComputeHMAC constants private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; + // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands. protected static RemotelyProvisionedComponentDevice rkp; protected static KMEncoder encoder; protected static KMDecoder decoder; @@ -1956,9 +1960,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe short keyLen = seProvider.cmacKDF( kmDataStore.getPresharedKey(), - ckdfLable, + ckdfLabel, (short) 0, - (short) ckdfLable.length, + (short) ckdfLabel.length, repository.getHeap(), concateBuffer, bufferIndex, @@ -2417,7 +2421,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); } KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); - short length = 0; try { asn1Decoder.validateDerSubject(issuer); } catch (KMException e) { @@ -3527,11 +3530,18 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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 > 128 - || macLen - < KMIntegerTag.getShortValue( - KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS])) { + || 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); @@ -3558,6 +3568,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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) { @@ -4450,7 +4463,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // is moved to a place where it is called on every boot. private void processInitStrongBoxCmd(APDU apdu) { short cmd = initStrongBoxCmd(apdu); - byte[] scratchPad = apdu.getBuffer(); short osVersion = KMArray.cast(cmd).get((short) 0); short osPatchLevel = KMArray.cast(cmd).get((short) 1); diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java index 3de1d0c..f58debc 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -906,7 +906,7 @@ public class KMKeymintDataStore implements KMUpgradable { @Override public void onRestore(Element element, short oldVersion, short currentVersion) { if (oldVersion <= KM_APPLET_PACKAGE_VERSION_1) { - // 1.0 to 3.0 Upgrade happens here. + // 1.0 to 3.1 Upgrade happens here. handlePreviousVersionUpgrade(element); return; } else if (oldVersion == KM_APPLET_PACKAGE_VERSION_2) { diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java index 0b94432..e33ecdf 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java @@ -181,21 +181,21 @@ public class RemotelyProvisionedComponentDevice { private void createAuthorizedEEKRoot() { if (authorizedEekRoots == null) { authorizedEekRoots = - new Object[] { - new byte[] { - (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, - (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, - (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, - (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, - (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, - (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, - (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, - (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, - (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, - (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, - (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f - }, - }; + new Object[] { + new byte[] { + (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, + (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, + (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, + (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, + (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, + (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, + (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, + (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, + (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, + (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, + (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f + }, + }; } } @@ -830,14 +830,24 @@ public class RemotelyProvisionedComponentDevice { signStructure = KMKeymasterApplet.encodeToApduBuffer( signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // 72 is the maximum ECDSA Signature length. + short maxEcdsaSignLen = 72; + short reclaimIndex = repository.allocReclaimableMemory(maxEcdsaSignLen); short len = seProvider.ecSign256( - deviceUniqueKeyPair, scratchPad, (short) 0, signStructure, scratchPad, signStructure); + deviceUniqueKeyPair, + scratchPad, + (short) 0, + signStructure, + repository.getHeap(), + reclaimIndex); + Util.arrayCopyNonAtomic(repository.getHeap(), reclaimIndex, scratchPad, (short) 0, len); + repository.reclaimMemory(maxEcdsaSignLen); len = KMAsn1Parser.instance() .decodeEcdsa256Signature( - KMByteBlob.instance(scratchPad, signStructure, len), scratchPad, signStructure); - signStructure = KMByteBlob.instance(scratchPad, signStructure, len); + KMByteBlob.instance(scratchPad, (short) 0, len), scratchPad, (short) 0); + signStructure = KMByteBlob.instance(scratchPad, (short) 0, len); /* Construct unprotected headers */ short unprotectedHeader = KMArray.instance((short) 0); -- cgit v1.2.3 From 0ce113724cf47ff0ecde308bd4af9294106f7167 Mon Sep 17 00:00:00 2001 From: subrahmanyaman Date: Fri, 23 Dec 2022 01:10:32 +0000 Subject: km200: Added missing java doc as per the review comments on aosp/2082578 Bug: b/242702664 Test: run vts -m VtsAidlKeyMintTarget Change-Id: Id405d209446790c193cebe9a381b95163c5f3653 --- .../javacard/keymaster/KMAndroidSEApplet.java | 7 +- .../javacard/keymaster/KMAttestationCertImpl.java | 16 +- .../javacard/keymaster/KMConfigurations.java | 4 + .../com/android/javacard/keymaster/KMUtils.java | 4 + .../com/android/javacard/seprovider/KMAESKey.java | 8 +- .../javacard/seprovider/KMAndroidSEProvider.java | 95 +- .../javacard/seprovider/KMAttestationCert.java | 2 +- .../javacard/seprovider/KMAttestationKey.java | 23 - .../javacard/seprovider/KMComputedHmacKey.java | 3 - .../javacard/seprovider/KMDataStoreConstants.java | 8 +- .../javacard/seprovider/KMDeviceUniqueKeyPair.java | 21 - .../javacard/seprovider/KMECDeviceUniqueKey.java | 54 - .../seprovider/KMECDeviceUniqueKeyPair.java | 55 + .../javacard/seprovider/KMECPrivateKey.java | 47 - .../seprovider/KMEcdsa256NoDigestSignature.java | 6 + .../com/android/javacard/seprovider/KMHmacKey.java | 8 +- .../src/com/android/javacard/seprovider/KMKey.java | 10 + .../android/javacard/seprovider/KMKeyObject.java | 4 + .../android/javacard/seprovider/KMMasterKey.java | 23 - .../javacard/seprovider/KMOperationImpl.java | 4 + .../android/javacard/seprovider/KMPoolManager.java | 10 +- .../javacard/seprovider/KMPreSharedKey.java | 23 - .../android/javacard/seprovider/KMRkpMacKey.java | 3 - .../seprovider/KMRsa2048NoDigestSignature.java | 1 + .../javacard/seprovider/KMRsaOAEPEncoding.java | 1 + .../android/javacard/seprovider/KMSEProvider.java | 46 +- .../android/javacard/seprovider/KMUpgradable.java | 1 + ready_se/google/keymint/KM200/Applet/README.md | 7 +- .../android/javacard/keymaster/KMAsn1Parser.java | 4 + .../com/android/javacard/keymaster/KMDecoder.java | 5 + .../com/android/javacard/keymaster/KMEncoder.java | 5 + .../javacard/keymaster/KMKeymasterApplet.java | 20 +- .../javacard/keymaster/KMKeymintDataStore.java | 66 +- .../src/com/android/javacard/keymaster/KMMap.java | 7 + .../com/android/javacard/keymaster/KMNInteger.java | 4 + .../KMRemotelyProvisionedComponentDevice.java | 1598 +++++++++++++++++++ .../android/javacard/keymaster/KMSemanticTag.java | 5 + .../android/javacard/keymaster/KMSimpleValue.java | 4 + .../RemotelyProvisionedComponentDevice.java | 1601 -------------------- 39 files changed, 1870 insertions(+), 1943 deletions(-) delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java delete mode 100644 ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java create mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java delete mode 100644 ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java index 5a8eb76..146b634 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -27,6 +27,11 @@ import org.globalplatform.upgrade.Element; import org.globalplatform.upgrade.OnUpgradeListener; import org.globalplatform.upgrade.UpgradeManager; +/** + * This class extends from KMKeymasterApplet which is main entry point to receive apdu commands. All + * the provision commands are processed here and later the data is handed over to the KMDataStore + * class which stores the data in the flash memory. + */ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { // Magic number version private static final byte KM_MAGIC_NUMBER = (byte) 0x82; @@ -305,8 +310,6 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis } private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { - // Re-purpose the apdu buffer as scratch pad. - byte[] scratchPad = apdu.getBuffer(); // Arguments short keyparams = KMKeyParameters.exp(); short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java index 90ec7f2..7245793 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -18,17 +18,17 @@ package com.android.javacard.keymaster; import com.android.javacard.seprovider.KMAESKey; import com.android.javacard.seprovider.KMAttestationCert; import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMMasterKey; +import com.android.javacard.seprovider.KMKey; import com.android.javacard.seprovider.KMSEProvider; import javacard.framework.JCSystem; import javacard.framework.Util; -// The class encodes strongbox generated amd signed attestation certificate. This only encodes -// required fields of the certificates. It is not meant to be generic X509 cert encoder. -// Whatever fields that are fixed are added as byte arrays. The Extensions are encoded as per -// the values. -// The certificate is assembled with leafs first and then the sequences. - +/** + * The class encodes strongbox generated and signed attestation certificates. It only encodes the + * required fields of the certificates. This class is not meant to be a generic X509 cert encoder. + * Any fields that are fixed are added as byte arrays. Extensions are encoded as per the values. The + * certificate is assembled with leafs first and then the sequences. + */ public class KMAttestationCertImpl implements KMAttestationCert { private static final byte MAX_PARAMS = 30; @@ -967,7 +967,7 @@ public class KMAttestationCertImpl implements KMAttestationCert { short appIdOff, short attestAppIdLen, byte resetSinceIdRotation, - KMMasterKey masterKey) { + KMKey masterKey) { // Concatenate T||C||R // temporal count T short temp = diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java index 3fb3653..738326b 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java @@ -15,6 +15,10 @@ */ package com.android.javacard.keymaster; +/** + * This class contains all the configuration values. Vendors can modify these values accordingly + * based on their environment. + */ public class KMConfigurations { // Machine types public static final byte LITTLE_ENDIAN = 0x00; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java index bed3ba7..95ee67f 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -18,6 +18,10 @@ package com.android.javacard.keymaster; import com.android.javacard.seprovider.KMException; import javacard.framework.Util; +/** + * This is a utility class which helps in converting date to UTC format and doing some arithmetic + * Operations. + */ public class KMUtils { // 64 bit unsigned calculations for time diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java index 06f1eaf..41059bb 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java @@ -18,7 +18,8 @@ package com.android.javacard.seprovider; import javacard.security.AESKey; import org.globalplatform.upgrade.Element; -public class KMAESKey implements KMMasterKey { +/** This is a wrapper class for AESKey. */ +public class KMAESKey implements KMKey { public AESKey aesKey; @@ -44,4 +45,9 @@ public class KMAESKey implements KMMasterKey { public static short getBackupObjectCount() { return (short) 1; } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } } diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java index 3b13b3e..6bfc8c3 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -8,7 +8,7 @@ * 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" (short)0IS, + * 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. @@ -38,6 +38,13 @@ import javacardx.crypto.Cipher; import org.globalplatform.upgrade.Element; import org.globalplatform.upgrade.UpgradeManager; +/** + * This class implements KMSEProvider and provides all the necessary crypto operations required to + * support the KeyMint specification. This class supports AES, 3DES, HMAC, RSA, ECDSA, ECDH + * algorithms additionally it also supports ECDSA_NO_DIGEST, RSA_NO_DIGEST and RSA_OAEP_MGF1_SHA1 + * and RSA_OAEP_MGF1_SHA256 algorithms. This class follows the pattern of Init-Update-Final for the + * crypto operations. + */ public class KMAndroidSEProvider implements KMSEProvider { public static final byte AES_GCM_TAG_LENGTH = 16; @@ -477,7 +484,7 @@ public class KMAndroidSEProvider implements KMSEProvider { } public HMACKey cmacKdf( - KMPreSharedKey preSharedKey, + KMKey preSharedKey, byte[] label, short labelStart, short labelLen, @@ -564,7 +571,7 @@ public class KMAndroidSEProvider implements KMSEProvider { @Override public short hmacKDF( - KMMasterKey masterkey, + KMKey masterkey, byte[] data, short dataStart, short dataLength, @@ -583,7 +590,7 @@ public class KMAndroidSEProvider implements KMSEProvider { @Override public boolean hmacVerify( - KMComputedHmacKey key, + KMKey key, byte[] data, short dataStart, short dataLength, @@ -943,7 +950,7 @@ public class KMAndroidSEProvider implements KMSEProvider { } @Override - public KMOperation initTrustedConfirmationSymmetricOperation(KMComputedHmacKey computedHmacKey) { + public KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey) { KMHmacKey key = (KMHmacKey) computedHmacKey; return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true); } @@ -1109,7 +1116,7 @@ public class KMAndroidSEProvider implements KMSEProvider { @Override public short cmacKDF( - KMPreSharedKey pSharedKey, + KMKey pSharedKey, byte[] label, short labelStart, short labelLen, @@ -1129,24 +1136,24 @@ public class KMAndroidSEProvider implements KMSEProvider { } @Override - public KMMasterKey createMasterKey(KMMasterKey masterKey, short keySizeBits) { + public KMKey createMasterKey(KMKey masterKey, short keySizeBits) { try { if (masterKey == null) { AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false); masterKey = new KMAESKey(key); - short keyLen = (short) (keySizeBits / 8); - getTrueRandomNumber(tmpArray, (short) 0, keyLen); - ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); } - return (KMMasterKey) masterKey; + short keyLen = (short) (keySizeBits / 8); + Util.arrayFillNonAtomic(tmpArray, (short) 0, keyLen, (byte) 0); + getTrueRandomNumber(tmpArray, (short) 0, keyLen); + ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); + return (KMKey) masterKey; } finally { clean(); } } @Override - public KMPreSharedKey createPreSharedKey( - KMPreSharedKey preSharedKey, byte[] keyData, short offset, short length) { + public KMKey createPreSharedKey(KMKey preSharedKey, byte[] keyData, short offset, short length) { short lengthInBits = (short) (length * 8); if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) { CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); @@ -1156,12 +1163,12 @@ public class KMAndroidSEProvider implements KMSEProvider { preSharedKey = new KMHmacKey(key); } ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length); - return (KMPreSharedKey) preSharedKey; + return (KMKey) preSharedKey; } @Override - public KMComputedHmacKey createComputedHmacKey( - KMComputedHmacKey computedHmacKey, byte[] keyData, short offset, short length) { + public KMKey createComputedHmacKey( + KMKey computedHmacKey, byte[] keyData, short offset, short length) { if (length != COMPUTED_HMAC_KEY_SIZE) { CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); } @@ -1171,7 +1178,7 @@ public class KMAndroidSEProvider implements KMSEProvider { computedHmacKey = new KMHmacKey(key); } ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length); - return (KMComputedHmacKey) computedHmacKey; + return (KMKey) computedHmacKey; } @Override @@ -1204,30 +1211,6 @@ public class KMAndroidSEProvider implements KMSEProvider { } } - @Override - public short ecSign256( - KMAttestationKey ecPrivKey, - byte[] inputDataBuf, - short inputDataStart, - short inputDataLength, - byte[] outputDataBuf, - short outputDataStart) { - Signature.OneShot signer = null; - try { - - signer = - Signature.OneShot.open( - MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); - signer.init(((KMECPrivateKey) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); - return signer.sign( - inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); - } finally { - if (signer != null) { - signer.close(); - } - } - } - @Override public short rsaSign256Pkcs1( byte[] secret, @@ -1395,8 +1378,8 @@ public class KMAndroidSEProvider implements KMSEProvider { } @Override - public short ecSign256( - KMDeviceUniqueKeyPair ecPrivKey, + public short signWithDeviceUniqueKey( + KMKey ecPrivKey, byte[] inputDataBuf, short inputDataStart, short inputDataLength, @@ -1407,7 +1390,8 @@ public class KMAndroidSEProvider implements KMSEProvider { signer = Signature.OneShot.open( MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); - signer.init(((KMECDeviceUniqueKey) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + signer.init( + ((KMECDeviceUniqueKeyPair) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); return signer.sign( inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); } finally { @@ -1418,8 +1402,8 @@ public class KMAndroidSEProvider implements KMSEProvider { } @Override - public KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( - KMDeviceUniqueKeyPair key, + public KMKey createRkpDeviceUniqueKeyPair( + KMKey key, byte[] pubKey, short pubKeyOff, short pubKeyLen, @@ -1429,18 +1413,17 @@ public class KMAndroidSEProvider implements KMSEProvider { if (key == null) { KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); poolMgr.initECKey(ecKeyPair); - key = new KMECDeviceUniqueKey(ecKeyPair); + key = new KMECDeviceUniqueKeyPair(ecKeyPair); } - ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKey) key).ecKeyPair.getPrivate(); - ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKey) key).ecKeyPair.getPublic(); + ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPublic(); ecKeyPair.setS(privKey, privKeyOff, privKeyLen); ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen); - return (KMDeviceUniqueKeyPair) key; + return (KMKey) key; } @Override - public KMRkpMacKey createRkpMacKey( - KMRkpMacKey rkpMacKey, byte[] keyData, short offset, short length) { + public KMKey createRkpMacKey(KMKey rkpMacKey, byte[] keyData, short offset, short length) { if (rkpMacKey == null) { HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); @@ -1494,7 +1477,7 @@ public class KMAndroidSEProvider implements KMSEProvider { KMHmacKey.onSave(element, (KMHmacKey) object); break; case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: - KMECDeviceUniqueKey.onSave(element, (KMECDeviceUniqueKey) object); + KMECDeviceUniqueKeyPair.onSave(element, (KMECDeviceUniqueKeyPair) object); break; case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: KMHmacKey.onSave(element, (KMHmacKey) object); @@ -1518,7 +1501,7 @@ public class KMAndroidSEProvider implements KMSEProvider { case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: return KMHmacKey.onRestore((HMACKey) element.readObject()); case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: - return KMECDeviceUniqueKey.onRestore((KeyPair) element.readObject()); + return KMECDeviceUniqueKeyPair.onRestore((KeyPair) element.readObject()); case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: return KMHmacKey.onRestore((HMACKey) element.readObject()); default: @@ -1538,7 +1521,7 @@ public class KMAndroidSEProvider implements KMSEProvider { primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); break; case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: - primitiveCount += KMECDeviceUniqueKey.getBackupPrimitiveByteCount(); + primitiveCount += KMECDeviceUniqueKeyPair.getBackupPrimitiveByteCount(); break; case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); @@ -1557,7 +1540,7 @@ public class KMAndroidSEProvider implements KMSEProvider { case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: return KMHmacKey.getBackupObjectCount(); case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: - return KMECDeviceUniqueKey.getBackupObjectCount(); + return KMECDeviceUniqueKeyPair.getBackupObjectCount(); case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: return KMHmacKey.getBackupObjectCount(); default: diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java index da60794..05801b0 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java @@ -73,7 +73,7 @@ public interface KMAttestationCert { short attestAppIdOff, short attestAppIdLen, byte resetSinceIdRotation, - KMMasterKey masterKey); + KMKey masterKey); /** * Set start time received from creation/activation time tag. Used for certificate's valid period. diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java deleted file mode 100644 index d941306..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -/** - * KMAttestationKey is a marker interface and the SE Provider has to implement this interface. - * Internally attestation key is stored as a Javacard EC key pair object, which will provide - * additional security. The attestation key is maintained by the SEProvider. - */ -public interface KMAttestationKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java deleted file mode 100644 index 8d406b4..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.android.javacard.seprovider; - -public interface KMComputedHmacKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java index a1d6454..61ddb36 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java @@ -1,9 +1,15 @@ package com.android.javacard.seprovider; +/** + * This class holds different interface type constants to differentiate between the instances of + * Computed Hmac key, device unique key pair, RKP Mac key, and master key when passed as generic + * objects. These constants are used in upgrade flow to retrieve the size of the object and + * primitive types saved and restored for respective key types. + */ public class KMDataStoreConstants { // INTERFACE Types public static final byte INTERFACE_TYPE_COMPUTED_HMAC_KEY = 0x01; - public static final byte INTERFACE_TYPE_ATTESTATION_KEY = 0x02; + // 0x02 reserved for INTERFACE_TYPE_ATTESTATION_KEY public static final byte INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR = 0x03; public static final byte INTERFACE_TYPE_MASTER_KEY = 0x04; public static final byte INTERFACE_TYPE_PRE_SHARED_KEY = 0x05; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java deleted file mode 100644 index 9bbccd8..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKeyPair.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -public interface KMDeviceUniqueKeyPair { - - short getPublicKey(byte[] buf, short offset); -} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java deleted file mode 100644 index 8adb1fb..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -import javacard.security.ECPublicKey; -import javacard.security.KeyPair; -import org.globalplatform.upgrade.Element; - -public class KMECDeviceUniqueKey implements KMDeviceUniqueKeyPair { - - public KeyPair ecKeyPair; - - @Override - public short getPublicKey(byte[] buf, short offset) { - ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); - return publicKey.getW(buf, offset); - } - - public KMECDeviceUniqueKey(KeyPair ecPair) { - ecKeyPair = ecPair; - } - - public static void onSave(Element element, KMECDeviceUniqueKey kmKey) { - element.write(kmKey.ecKeyPair); - } - - public static KMECDeviceUniqueKey onRestore(KeyPair ecKey) { - if (ecKey == null) { - return null; - } - return new KMECDeviceUniqueKey(ecKey); - } - - public static short getBackupPrimitiveByteCount() { - return (short) 0; - } - - public static short getBackupObjectCount() { - return (short) 1; - } -} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java new file mode 100644 index 0000000..0e430a3 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java @@ -0,0 +1,55 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for KeyPair. */ +public class KMECDeviceUniqueKeyPair implements KMKey { + + public KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKeyPair(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECDeviceUniqueKeyPair kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECDeviceUniqueKeyPair onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECDeviceUniqueKeyPair(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java deleted file mode 100644 index 35c687b..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -import javacard.security.KeyPair; -import org.globalplatform.upgrade.Element; - -public class KMECPrivateKey implements KMAttestationKey { - - public KeyPair ecKeyPair; - - public KMECPrivateKey(KeyPair ecPair) { - ecKeyPair = ecPair; - } - - public static void onSave(Element element, KMECPrivateKey kmKey) { - element.write(kmKey.ecKeyPair); - } - - public static KMECPrivateKey onRestore(KeyPair ecKey) { - if (ecKey == null) { - return null; - } - return new KMECPrivateKey(ecKey); - } - - public static short getBackupPrimitiveByteCount() { - return (short) 0; - } - - public static short getBackupObjectCount() { - return (short) 1; - } -} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java index e9b95ee..83774ab 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java @@ -22,6 +22,10 @@ import javacard.security.MessageDigest; import javacard.security.Signature; import javacardx.crypto.Cipher; +/** + * This class provides support for ECDSA_NO_DIGEST signature algorithm. Added this because javacard + * 3.0.5 does not support this + */ public class KMEcdsa256NoDigestSignature extends Signature { public static final byte ALG_ECDSA_NODIGEST = (byte) 0x67; @@ -31,6 +35,8 @@ public class KMEcdsa256NoDigestSignature extends Signature { public KMEcdsa256NoDigestSignature(byte alg) { algorithm = alg; + // There is no constant for no digest so ALG_ECDSA_SHA_256 is used. However, + // signPreComputedHash is used for signing which is equivalent to no digest sign. inst = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); } diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java index 3d25143..e938a2b 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java @@ -18,7 +18,8 @@ package com.android.javacard.seprovider; import javacard.security.HMACKey; import org.globalplatform.upgrade.Element; -public class KMHmacKey implements KMPreSharedKey, KMComputedHmacKey, KMRkpMacKey { +/** This is a wrapper class for HMACKey. */ +public class KMHmacKey implements KMKey { public HMACKey hmacKey; @@ -44,4 +45,9 @@ public class KMHmacKey implements KMPreSharedKey, KMComputedHmacKey, KMRkpMacKey public static short getBackupObjectCount() { return (short) 1; } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } } diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java new file mode 100644 index 0000000..9894382 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This interface helps to decouple Javacard internal key objects from the keymaster package. Using + * Javacard key objects provides security by providing protection against side channel attacks. + * KMAESKey, KMECDeviceUniqueKey and KMHmacKey implements this interface. + */ +public interface KMKey { + short getPublicKey(byte[] buf, short offset); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java index 26edaa2..a37da08 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java @@ -1,5 +1,9 @@ package com.android.javacard.seprovider; +/** + * This class holds the KeyObject and its associated algorithm value. Each KMKeyObject is tied to + * one of the crypto operations. + */ public class KMKeyObject { public byte algorithm; public Object keyObjectInst; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java deleted file mode 100644 index 27afb3d..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -/** - * KMMasterKey is a marker interface and the SE Provider has to implement this interface. Internally - * Masterkey is stored as a Javacard AES key object, which will provide additional security. The - * master key is maintained by the SEProvider. - */ -public interface KMMasterKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java index b2a2421..8059e44 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -25,6 +25,10 @@ import javacard.security.Signature; import javacardx.crypto.AEADCipher; import javacardx.crypto.Cipher; +/** + * This class contains the actual implementation of all the crypto operations. It internally uses + * the Javacard crypto library to perform the operations. + */ public class KMOperationImpl implements KMOperation { private static final byte ALG_TYPE_OFFSET = 0x00; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java index d4d98e3..8ca2664 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -29,7 +29,15 @@ import javacard.security.Signature; import javacardx.crypto.AEADCipher; import javacardx.crypto.Cipher; -/** This class manages all the pool instances. */ +/** + * This class creates and manages all the cipher, signer, key agreement, operation and trusted + * confirmation pool instances. Each cipher or signer pool can hold a maximum of 4 instances per + * algorithm; however, only one instance of each algorithm is created initially and if required more + * instances are created dynamically. A maximum of four operations can be performed simultaneously. + * Upon reaching the maximum limit of 4, further operations or crypto instances will throw a + * TOO_MANY_OPERATIONS error. TrustedConfirmation pool is to support any operation which has the + * TRUSTED_CONFIRMATION tag in its key parameters. + */ public class KMPoolManager { public static final byte MAX_OPERATION_INSTANCES = 4; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java deleted file mode 100644 index 8f94c82..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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" (short)0IS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.javacard.seprovider; - -/** - * KMPreSharedKey is a marker interface and the SE Provider has to implement this interface. - * Internally Preshared key is stored as a Javacard HMac key object, which will provide additional - * security. The pre-shared key is maintained by the SEProvider. - */ -public interface KMPreSharedKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java deleted file mode 100644 index fe6ad84..0000000 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRkpMacKey.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.android.javacard.seprovider; - -public interface KMRkpMacKey {} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java index 287a449..4890ce2 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -22,6 +22,7 @@ import javacard.security.MessageDigest; import javacard.security.Signature; import javacardx.crypto.Cipher; +/** This class provides support for RSA_NO_DIGEST signature algorithm. */ public class KMRsa2048NoDigestSignature extends Signature { public static final byte ALG_RSA_SIGN_NOPAD = (byte) 0x65; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java index 74322bd..bf34ea1 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java @@ -22,6 +22,7 @@ import javacard.security.Key; import javacard.security.MessageDigest; import javacardx.crypto.Cipher; +/** This class has the implementation for RSA_OAEP decoding algorithm. */ public class KMRsaOAEPEncoding extends Cipher { public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 = (byte) 0x1E; diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java index 1a564cc..9313c04 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java @@ -92,7 +92,7 @@ public interface KMSEProvider { * @param computedHmacKey Instance of the computed Hmac key. * @return instance of KMOperation. */ - KMOperation initTrustedConfirmationSymmetricOperation(KMComputedHmacKey computedHmacKey); + KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey); /** * Verify that the imported key is valid. If the algorithm and/or keysize are not supported then @@ -263,7 +263,7 @@ public interface KMSEProvider { * @return length of the derived key buffer in bytes. */ short cmacKDF( - KMPreSharedKey hmacKey, + KMKey hmacKey, byte[] label, short labelStart, short labelLen, @@ -328,7 +328,7 @@ public interface KMSEProvider { * @return length of the signature buffer in bytes. */ short hmacKDF( - KMMasterKey masterkey, + KMKey masterkey, byte[] data, short dataStart, short dataLength, @@ -350,7 +350,7 @@ public interface KMSEProvider { * @return true if the signature matches. */ boolean hmacVerify( - KMComputedHmacKey hmacKey, + KMKey hmacKey, byte[] data, short dataStart, short dataLength, @@ -389,25 +389,6 @@ public interface KMSEProvider { byte[] outputDataBuf, short outputDataStart); - /** - * This is a oneshot operation that signs the data using EC private key. - * - * @param ecPrivKey of KMAttestationKey. - * @param inputDataBuf is the buffer of the input data. - * @param inputDataStart is the start of the input data buffer. - * @param inputDataLength is the length of the inpur data buffer in bytes. - * @param outputDataBuf is the output buffer that contains the signature. - * @param outputDataStart is the start of the output data buffer. - * @return length of the decrypted data. - */ - short ecSign256( - KMAttestationKey ecPrivKey, - byte[] inputDataBuf, - short inputDataStart, - short inputDataLength, - byte[] outputDataBuf, - short outputDataStart); - /** * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 * @@ -498,8 +479,8 @@ public interface KMSEProvider { * @param outputDataStart is the start of the output data buffer. * @return length of the decrypted data. */ - short ecSign256( - KMDeviceUniqueKeyPair ecPrivKey, + short signWithDeviceUniqueKey( + KMKey deviceUniqueKey, byte[] inputDataBuf, short inputDataStart, short inputDataLength, @@ -688,7 +669,7 @@ public interface KMSEProvider { * @param keySizeBits key size in bits. * @return An instance of KMMasterKey. */ - KMMasterKey createMasterKey(KMMasterKey masterKey, short keySizeBits); + KMKey createMasterKey(KMKey masterKey, short keySizeBits); /** * This function creates an HMACKey and initializes the key with the provided input key data. @@ -698,8 +679,7 @@ public interface KMSEProvider { * @param length length of the buffer. * @return An instance of the KMComputedHmacKey. */ - KMComputedHmacKey createComputedHmacKey( - KMComputedHmacKey computedHmacKey, byte[] keyData, short offset, short length); + KMKey createComputedHmacKey(KMKey computedHmacKey, byte[] keyData, short offset, short length); /** Returns true if factory provisioned attestation key is supported. */ boolean isAttestationKeyProvisioned(); @@ -722,8 +702,8 @@ public interface KMSEProvider { * @param privKeyLen private key buffer length. * @return instance of KMDeviceUniqueKey. */ - KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( - KMDeviceUniqueKeyPair key, + KMKey createRkpDeviceUniqueKeyPair( + KMKey key, byte[] pubKey, short pubKeyOff, short pubKeyLen, @@ -753,8 +733,7 @@ public interface KMSEProvider { * @param length is the length of the key. * @return instance of KMPresharedKey. */ - KMPreSharedKey createPreSharedKey( - KMPreSharedKey presharedKey, byte[] key, short offset, short length); + KMKey createPreSharedKey(KMKey presharedKey, byte[] key, short offset, short length); /** * This function saves the key objects while upgrade. @@ -799,6 +778,5 @@ public interface KMSEProvider { * @param length length of the buffer. * @return An instance of the KMRkpMacKey. */ - KMRkpMacKey createRkpMacKey( - KMRkpMacKey createComputedHmacKey, byte[] keyData, short offset, short length); + KMKey createRkpMacKey(KMKey createComputedHmacKey, byte[] keyData, short offset, short length); } diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java index 69536ab..420b2c7 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java @@ -17,6 +17,7 @@ package com.android.javacard.seprovider; import org.globalplatform.upgrade.Element; +/** This interface helps in storing and restoring the applet data during the applet upgrades. */ public interface KMUpgradable { void onSave(Element ele); diff --git a/ready_se/google/keymint/KM200/Applet/README.md b/ready_se/google/keymint/KM200/Applet/README.md index ace6950..c14a369 100644 --- a/ready_se/google/keymint/KM200/Applet/README.md +++ b/ready_se/google/keymint/KM200/Applet/README.md @@ -1,14 +1,15 @@ # JavaCardKeymaster Applet -This directory contains the implementation of the Keymint 1.0 +This directory contains the implementation of the KeyMint 2.0 interface, in the form of a JavaCard 3.0.5 applet which runs in a secure -element. It must be deployed in conjuction with the associated HAL, +element. It must be deployed in conjunction with the associated HAL, which mediates between Android Keystore and this applet. # Supported Features! - - Keymint 1.0 supported functions for required VTS compliance. + - KeyMint 2.0 supported functions for required VTS compliance. - SharedSecret 1.0 supported functions for required VTS compliance. + - RemotelyProvisionedComponent 2.0 supported functions for required VTS compliance. # Not supported features - Factory provisioned attestation key will not be supported in this applet. diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java index 8ff1bc3..b3eacd6 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -4,6 +4,10 @@ import com.android.javacard.seprovider.KMException; import javacard.framework.JCSystem; import javacard.framework.Util; +/** + * This is a utility class that helps in parsing the PKCS8 encoded RSA and EC keys, certificate + * subject, subjectPublicKey info and ECDSA signatures. + */ public class KMAsn1Parser { public static final byte ASN1_OCTET_STRING = 0x04; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java index 10c7d29..e7dc21d 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -22,6 +22,11 @@ import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; +/** + * This class decodes the CBOR format data into a KMType structure. It interprets the input CBOR + * format using the input expression provided. Validation of KeyMint tags and tag types happens in + * the process of decoding, while constructing the subtype of a KMType structure. + */ public class KMDecoder { // major types diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java index 7405e06..65394bd 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -22,6 +22,11 @@ import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.Util; +/** + * This class encodes KMType structures to a cbor format data recursively. Encoded bytes are written + * on the buffer provided by the caller. An exception will be thrown if the encoded data length is + * greater than the buffer length provided. + */ public class KMEncoder { // major types diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index e196371..adc060f 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -18,8 +18,8 @@ package com.android.javacard.keymaster; import com.android.javacard.seprovider.KMAttestationCert; import com.android.javacard.seprovider.KMDataStoreConstants; -import com.android.javacard.seprovider.KMDeviceUniqueKeyPair; 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; @@ -33,9 +33,9 @@ import javacard.security.CryptoException; import javacardx.apdu.ExtendedLength; /** - * KMKeymasterApplet implements the javacard applet. It creates repository and other install time - * objects. It also implements the keymaster state machine and handles javacard applet life cycle - * events. + * 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 { @@ -264,7 +264,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // ComputeHMAC constants private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands. - protected static RemotelyProvisionedComponentDevice rkp; + protected static KMRemotelyProvisionedComponentDevice rkp; protected static KMEncoder encoder; protected static KMDecoder decoder; protected static KMRepository repository; @@ -303,7 +303,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // initialize default values initHmacNonceAndSeed(); rkp = - new RemotelyProvisionedComponentDevice( + new KMRemotelyProvisionedComponentDevice( encoder, decoder, repository, seProvider, kmDataStore); } @@ -1069,7 +1069,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe if (!testMode && kmDataStore.isProvisionLocked()) { KMException.throwIt(KMError.STATUS_FAILED); } - KMDeviceUniqueKeyPair deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(testMode); + KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(testMode); short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); short coseKey = KMCose.constructCoseKey( @@ -1139,7 +1139,8 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); // do sign short len = - seProvider.ecSign256(deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp); + seProvider.signWithDeviceUniqueKey( + deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp); len = KMAsn1Parser.instance() .decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp); @@ -1817,6 +1818,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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); } diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java index f58debc..28bb810 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -1,12 +1,8 @@ package com.android.javacard.keymaster; -import com.android.javacard.seprovider.KMComputedHmacKey; import com.android.javacard.seprovider.KMDataStoreConstants; -import com.android.javacard.seprovider.KMDeviceUniqueKeyPair; import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMMasterKey; -import com.android.javacard.seprovider.KMPreSharedKey; -import com.android.javacard.seprovider.KMRkpMacKey; +import com.android.javacard.seprovider.KMKey; import com.android.javacard.seprovider.KMSEProvider; import com.android.javacard.seprovider.KMUpgradable; import javacard.framework.ISO7816; @@ -15,6 +11,12 @@ import javacard.framework.JCSystem; import javacard.framework.Util; import org.globalplatform.upgrade.Element; +/** + * This is a storage class which helps in storing the provisioned data, ROT, OS version, Boot patch + * level, Vendor Patchlevel, HMAC nonce, computed shared secret, 8 auth tags, device-locked, + * device-locked timestamp and device-locked password only. Only the provisioned data is restored + * back during applet upgrades and the remaining data is flushed. + */ public class KMKeymintDataStore implements KMUpgradable { // Data table configuration @@ -93,12 +95,12 @@ public class KMKeymintDataStore implements KMUpgradable { private KMRepository repository; private byte[] additionalCertChain; private byte[] bcc; - private KMMasterKey masterKey; - private KMDeviceUniqueKeyPair testDeviceUniqueKeyPair; - private KMDeviceUniqueKeyPair deviceUniqueKeyPair; - private KMPreSharedKey preSharedKey; - private KMComputedHmacKey computedHmacKey; - private KMRkpMacKey rkpMacKey; + private KMKey masterKey; + private KMKey testDeviceUniqueKeyPair; + private KMKey deviceUniqueKeyPair; + private KMKey preSharedKey; + private KMKey computedHmacKey; + private KMKey rkpMacKey; private byte[] oemRootPublicKey; private short provisionStatus; @@ -527,14 +529,20 @@ public class KMKeymintDataStore implements KMUpgradable { buf[offset] = state; } - public KMMasterKey createMasterKey(short keySizeBits) { + // The master key should only be generated during applet installation and + // during a device factory reset event. + public KMKey createMasterKey(short keySizeBits) { if (masterKey == null) { masterKey = seProvider.createMasterKey(masterKey, keySizeBits); } - return (KMMasterKey) masterKey; + return (KMKey) masterKey; } - public KMMasterKey getMasterKey() { + public KMKey regenerateMasterKey() { + return seProvider.createMasterKey(masterKey, KMKeymasterApplet.MASTER_KEY_SIZE); + } + + public KMKey getMasterKey() { return masterKey; } @@ -547,7 +555,7 @@ public class KMKeymintDataStore implements KMUpgradable { } } - public KMPreSharedKey getPresharedKey() { + public KMKey getPresharedKey() { if (preSharedKey == null) { KMException.throwIt(KMError.INVALID_DATA); } @@ -565,14 +573,14 @@ public class KMKeymintDataStore implements KMUpgradable { } } - public KMComputedHmacKey getComputedHmacKey() { + public KMKey getComputedHmacKey() { if (computedHmacKey == null) { KMException.throwIt(KMError.INVALID_DATA); } return computedHmacKey; } - public KMDeviceUniqueKeyPair createRkpTestDeviceUniqueKeyPair( + public KMKey createRkpTestDeviceUniqueKeyPair( byte[] pubKey, short pubKeyOff, short pubKeyLen, @@ -596,7 +604,7 @@ public class KMKeymintDataStore implements KMUpgradable { return testDeviceUniqueKeyPair; } - public KMDeviceUniqueKeyPair createRkpDeviceUniqueKeyPair( + public KMKey createRkpDeviceUniqueKeyPair( byte[] pubKey, short pubKeyOff, short pubKeyLen, @@ -614,8 +622,8 @@ public class KMKeymintDataStore implements KMUpgradable { return deviceUniqueKeyPair; } - public KMDeviceUniqueKeyPair getRkpDeviceUniqueKeyPair(boolean testMode) { - return ((KMDeviceUniqueKeyPair) (testMode ? testDeviceUniqueKeyPair : deviceUniqueKeyPair)); + public KMKey getRkpDeviceUniqueKeyPair(boolean testMode) { + return ((KMKey) (testMode ? testDeviceUniqueKeyPair : deviceUniqueKeyPair)); } public void createRkpMacKey(byte[] keydata, short offset, short length) { @@ -626,7 +634,7 @@ public class KMKeymintDataStore implements KMUpgradable { } } - public KMRkpMacKey getRkpMacKey() { + public KMKey getRkpMacKey() { if (rkpMacKey == null) { KMException.throwIt(KMError.INVALID_DATA); } @@ -954,11 +962,11 @@ public class KMKeymintDataStore implements KMUpgradable { bcc = (byte[]) element.readObject(); // Read Key Objects - masterKey = (KMMasterKey) seProvider.onRestore(element); + masterKey = (KMKey) seProvider.onRestore(element); seProvider.onRestore(element); // pop computedHmacKey - preSharedKey = (KMPreSharedKey) seProvider.onRestore(element); - deviceUniqueKeyPair = (KMDeviceUniqueKeyPair) seProvider.onRestore(element); - rkpMacKey = (KMRkpMacKey) seProvider.onRestore(element); + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); handleProvisionStatusUpgrade(oldDataTable, oldDataIndex); } @@ -981,10 +989,10 @@ public class KMKeymintDataStore implements KMUpgradable { bcc = (byte[]) element.readObject(); oemRootPublicKey = (byte[]) element.readObject(); // Read Key Objects - masterKey = (KMMasterKey) seProvider.onRestore(element); - preSharedKey = (KMPreSharedKey) seProvider.onRestore(element); - deviceUniqueKeyPair = (KMDeviceUniqueKeyPair) seProvider.onRestore(element); - rkpMacKey = (KMRkpMacKey) seProvider.onRestore(element); + masterKey = (KMKey) seProvider.onRestore(element); + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); } public void getProvisionStatus(byte[] dataTable, byte[] scratchpad, short offset) { diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java index 4583e02..2418204 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -20,6 +20,13 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; +/** + * KMMap represents an array of a KMType key and a KMType value. Map is the sequence of pairs. Each + * pair is one or more sub-types of KMType. The KMMap instance maps to the CBOR type map. KMMap is a + * KMType and it further extends the value field in TLV_HEADER as MAP_HEADER struct{ short + * subType;short length;} followed by a sequence of pairs. Each pair contains a key and a value as + * short pointers to KMType instances. + */ public class KMMap extends KMType { public static final short ANY_MAP_LENGTH = 0x1000; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java index 6246f21..3a6404e 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -20,6 +20,10 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; +/** + * Represents 8-bit, 16-bit, 32-bit and 64-bit signed integer. It corresponds to CBOR int type. + * struct{byte NEG_INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ public class KMNInteger extends KMInteger { public static final byte SIGNED_MASK = (byte) 0x80; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java new file mode 100644 index 0000000..b844ce6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java @@ -0,0 +1,1598 @@ +/* + * 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 { + + // Device Info labels + public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; + public static final byte[] MANUFACTURER = { + 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72 + }; + public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65}; + public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + public static final byte[] BOOTLOADER_STATE = { + 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65 + }; + public static final byte[] VB_META_DIGEST = { + 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74 + }; + public static final byte[] OS_VERSION = { + 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E + }; + 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 + }; + public static final byte[] BOOT_PATCH_LEVEL = { + 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + 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 + }; + public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + public static final byte[] SECURITY_LEVEL = { + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64}; + // Verified boot state values + public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; + public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; + // Boot loader state values + public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // Device info CDDL schema version + public static final byte DI_SCHEMA_VERSION = 2; + public static final byte[] DI_SECURITY_LEVEL = { + 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78 + }; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + private static final byte TRUE = 0x01; + private static final byte FALSE = 0x00; + // RKP Version + private static final short RKP_VERSION = (short) 0x02; + // Boot params + 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; + private static final boolean IS_ACC_SUPPORTED_IN_RKP_SERVER = false; + private static final short MAX_SEND_DATA = 512; + private static final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + private static final byte[] uniqueId = { + 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, 0x20, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, + 0x74 + }; // "strongbox keymint" + // more data or no data + private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve + private static final byte NO_DATA = 0x00; + // Response processing states + private static final byte START_PROCESSING = 0x00; + private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; + private static final byte PROCESSING_BCC_COMPLETE = 0x04; + private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; // Additional certificate chain. + private static final byte PROCESSING_ACC_COMPLETE = 0x0A; + // data table + private static final short DATA_SIZE = 512; + private static final byte DATA_INDEX_SIZE = 11; + // data offsets + private static final byte EPHEMERAL_MAC_KEY = 0; + private static final byte TOTAL_KEYS_TO_SIGN = 1; + private static final byte KEYS_TO_SIGN_COUNT = 2; + private static final byte TEST_MODE = 3; + private static final byte EEK_KEY = 4; + private static final byte EEK_KEY_ID = 5; + private static final byte CHALLENGE = 6; + private static final byte GENERATE_CSR_PHASE = 7; + private static final byte EPHEMERAL_PUB_KEY = 8; + private static final byte RESPONSE_PROCESSING_STATE = 9; + private static final byte ACC_PROCESSED_LENGTH = 10; + + // data item sizes + private static final byte MAC_KEY_SIZE = 32; + private static final byte SHORT_SIZE = 2; + private static final byte BYTE_SIZE = 1; + private static final byte TEST_MODE_SIZE = 1; + // generate csr states + private static final byte BEGIN = 0x01; + private static final byte UPDATE = 0x02; + private static final byte FINISH = 0x04; + private static final byte GET_RESPONSE = 0x06; + + // RKP mac key size + private static final byte RKP_MAC_KEY_SIZE = 32; + public static Object[] authorizedEekRoots; + public short[] rkpTmpVariables; + // variables + private byte[] data; + private KMEncoder encoder; + private KMDecoder decoder; + private KMRepository repository; + private KMSEProvider seProvider; + private KMKeymintDataStore storeDataInst; + private Object[] operation; + 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; + createAuthorizedEEKRoot(); + } + + private void createAuthorizedEEKRoot() { + if (authorizedEekRoots == null) { + authorizedEekRoots = + new Object[] { + new byte[] { + (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, + (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, + (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, + (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, + (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, + (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, + (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, + (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, + (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, + (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, + (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f + }, + }; + } + } + + 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 short getEntryLength(short index) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(data, index); + } + + private void processGetRkpHwInfoCmd(APDU apdu) { + // Make the response + // Author name - Google. + short respPtr = KMArray.instance((short) 5); + 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(google, (short) 0, (short) google.length)); + resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_P256)); + resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); + 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 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, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Generate ephemeral mac key. + short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, MAC_KEY_SIZE); + seProvider.newRandomNumber(data, dataEntryIndex, MAC_KEY_SIZE); + // Initialize hmac operation. + initHmacOperation(); + // Partially encode CoseMac structure with partial payload. + constructPartialPubKeysToSignMac( + scratchPad, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), + KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort()); + // Store the total keys in data table. + dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); + Util.setShort( + data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); + // Store the test mode value in data table. + dataEntryIndex = createEntry(TEST_MODE, TEST_MODE_SIZE); + data[dataEntryIndex] = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 2)).getValue()) + ? TRUE + : FALSE; + // Store the current csr status, which is BEGIN. + createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); + updateState(BEGIN); + // Send response. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + 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); + 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 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 Hmac update with input as encoded CoseKey. + ((KMOperation) operation[0]).update(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. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateEekChain(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + 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 arrSignPtr = KMArray.exp(arrInst); + arrInst = KMKeymasterApplet.receiveIncoming(apdu, arrSignPtr); + if (KMArray.cast(arrInst).length() == 0) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Validate eek chain. + short eekKey = validateAndExtractEekPub(arrInst, scratchPad); + // Store eek public key and eek id in the data table. + short eekKeyId = KMCoseKey.cast(eekKey).getKeyIdentifier(); + short dataEntryIndex = createEntry(EEK_KEY_ID, KMByteBlob.cast(eekKeyId).length()); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(eekKeyId).getBuffer(), + KMByteBlob.cast(eekKeyId).getStartOff(), + data, + dataEntryIndex, + KMByteBlob.cast(eekKeyId).length()); + // Convert the coseKey to a public key. + short len = KMCoseKey.cast(eekKey).getEcdsa256PublicKey(scratchPad, (short) 0); + dataEntryIndex = createEntry(EEK_KEY, len); + Util.arrayCopyNonAtomic(scratchPad, (short) 0, data, dataEntryIndex, len); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateChallenge(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMByteBlob.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Store the challenge in the data table. + short challenge = KMArray.cast(arr).get((short) 0); + short challengeLen = KMByteBlob.cast(challenge).length(); + if (challengeLen > 64) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short dataEntryIndex = createEntry(CHALLENGE, challengeLen); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(challenge).getBuffer(), + KMByteBlob.cast(challenge).getStartOff(), + data, + dataEntryIndex, + challengeLen); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + // This function returns pubKeysToSignMac, deviceInfo and partially constructed protected data + // wrapped inside byte blob. The partial protected data contains Headers and encrypted signedMac. + 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); + // release operation + releaseOperation(); + short pubKeysToSignMac = KMByteBlob.instance(scratchPad, (short) 0, len); + // Create DeviceInfo + short deviceInfo = createDeviceInfo(scratchPad); + // Generate Nonce for AES-GCM + seProvider.newRandomNumber(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + short nonce = + KMByteBlob.instance(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + // Initializes cipher instance. + initAesGcmOperation(scratchPad, nonce); + // Encode Enc_Structure as additional data for AES-GCM. + processAesGcmUpdateAad(scratchPad); + short partialPayloadLen = processSignedMac(scratchPad, pubKeysToSignMac, deviceInfo); + short partialCipherText = KMByteBlob.instance(scratchPad, (short) 0, partialPayloadLen); + short coseEncryptProtectedHeader = getCoseEncryptProtectedHeader(scratchPad); + short coseEncryptUnProtectedHeader = getCoseEncryptUnprotectedHeader(scratchPad, nonce); + len = + KMKeymasterApplet.encodeToApduBuffer( + deviceInfo, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short encodedDeviceInfo = KMByteBlob.instance(scratchPad, (short) 0, len); + updateState(FINISH); + short arr = KMArray.instance((short) 7); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, pubKeysToSignMac); + KMArray.cast(arr).add((short) 2, encodedDeviceInfo); + KMArray.cast(arr).add((short) 3, coseEncryptProtectedHeader); + KMArray.cast(arr).add((short) 4, coseEncryptUnProtectedHeader); + KMArray.cast(arr).add((short) 5, partialCipherText); + KMArray.cast(arr).add((short) 6, KMInteger.uint_8(MORE_DATA)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processGetResponse(APDU apdu) throws Exception { + try { + // The prior state should be FINISH. + validateState((byte) (FINISH | GET_RESPONSE)); + byte[] scratchPad = apdu.getBuffer(); + short len = 0; + short recipientStructure = KMArray.instance((short) 0); + byte moreData = MORE_DATA; + byte state = getCurrentOutputProcessingState(); + switch (state) { + case START_PROCESSING: + case PROCESSING_BCC_IN_PROGRESS: + len = processBcc(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_BCC_COMPLETE: + case PROCESSING_ACC_IN_PROGRESS: + len = processAdditionalCertificateChain(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_ACC_COMPLETE: + recipientStructure = processRecipientStructure(scratchPad); + len = processFinalData(scratchPad); + moreData = NO_DATA; + releaseOperation(); + clearDataTable(); + break; + default: + KMException.throwIt(KMError.INVALID_STATE); + } + short data = KMByteBlob.instance(scratchPad, (short) 0, len); + short arr = KMArray.instance((short) 4); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, data); + KMArray.cast(arr).add((short) 2, recipientStructure); + // represents there is more output to retrieve + KMArray.cast(arr).add((short) 3, KMInteger.uint_8(moreData)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + 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_UPDATE_EEK_CHAIN_CMD: + processUpdateEekChain(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_CHALLENGE_CMD: + processUpdateChallenge(apdu); + break; + case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: + processFinishSendData(apdu); + break; + case KMKeymasterApplet.INS_GET_RESPONSE_CMD: + processGetResponse(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } + + private boolean isAdditionalCertificateChainPresent() { + if (!IS_ACC_SUPPORTED_IN_RKP_SERVER || (TRUE == data[getEntry(TEST_MODE)])) { + // Don't include AdditionalCertificateChain in ProtectedData if either + // 1. RKP server does not support processing of X.509 Additional Certificate Chain. + // 2. Requested CSR for test mode. + return false; + } + return (storeDataInst.getAdditionalCertChainLength() == 0 ? false : true); + } + + private short processFinalData(byte[] scratchPad) { + // Call finish on AES GCM Cipher + short empty = repository.alloc((short) 0); + short len = + ((KMOperation) operation[0]) + .finish(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); + return len; + } + + 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) { + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : 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, + KMType.INVALID_VALUE, + 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; + } + + /** + * This function validates the EEK Chain and extracts the leaf public key, which is used to + * generate shared secret using ECDH. + * + * @param eekArr EEK cert chain array pointer. + * @param scratchPad Scratch buffer used to store temp results. + * @return CoseKey instance. + */ + private short validateAndExtractEekPub(short eekArr, byte[] scratchPad) { + short leafPubKey = 0; + try { + leafPubKey = + KMKeymasterApplet.validateCertChain( + (TRUE == data[getEntry(TEST_MODE)]) ? false : true, // validate EEK root + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + eekArr, + scratchPad, + authorizedEekRoots); + } catch (KMException e) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + return leafPubKey; + } + + 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; + } + + /** + * This function constructs a Mac Structure, encode it and signs the encoded buffer with the + * ephemeral mac key. + */ + private void constructPartialPubKeysToSignMac( + byte[] scratchPad, short arrayLength, short encodedCoseKeysLen) { + short ptr; + short len; + 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 + ptr = + KMCose.constructCoseMacStructure( + protectedHeader, KMByteBlob.instance((short) 0), KMType.INVALID_VALUE); + // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 + len = + KMKeymasterApplet.encodeToApduBuffer( + ptr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct partial payload - Bstr Header + Array Header + // The maximum combined length of bstr header and array header length is 6 bytes. + // The lengths will never exceed Max SHORT value. + short arrPtr = KMArray.instance(arrayLength); + for (short i = 0; i < arrayLength; i++) { + KMArray.cast(arrPtr).add(i, KMType.INVALID_VALUE); + } + arrayLength = encoder.getEncodedLength(arrPtr); + short bufIndex = repository.alloc((short) 6); + short partialPayloadLen = + encoder.encodeByteBlobHeader( + (short) (arrayLength + encodedCoseKeysLen), repository.getHeap(), bufIndex, (short) 3); + + partialPayloadLen += + encoder.encode( + arrPtr, + repository.getHeap(), + (short) (bufIndex + partialPayloadLen), + repository.getHeapReclaimIndex()); + Util.arrayCopyNonAtomic(repository.getHeap(), bufIndex, scratchPad, len, partialPayloadLen); + ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); + } + + private short createSignedMac( + KMKey deviceUniqueKeyPair, byte[] scratchPad, short deviceMapPtr, short pubKeysToSign) { + // Challenge + short dataEntryIndex = getEntry(CHALLENGE); + short challengePtr = KMByteBlob.instance(data, dataEntryIndex, getEntryLength(CHALLENGE)); + // Ephemeral mac key + dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + short ephmeralMacKey = + KMByteBlob.instance(data, dataEntryIndex, getEntryLength(EPHEMERAL_MAC_KEY)); + + /* Prepare AAD */ + short aad = KMArray.instance((short) 3); + KMArray.cast(aad).add((short) 0, challengePtr); + KMArray.cast(aad).add((short) 1, deviceMapPtr); + KMArray.cast(aad).add((short) 2, pubKeysToSign); + aad = + KMKeymasterApplet.encodeToApduBuffer( + aad, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + aad = KMByteBlob.instance(scratchPad, (short) 0, aad); + + /* 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 cose sign structure */ + short signStructure = KMCose.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); + signStructure = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // 72 is the maximum ECDSA Signature length. + short maxEcdsaSignLen = 72; + short reclaimIndex = repository.allocReclaimableMemory(maxEcdsaSignLen); + short len = + seProvider.signWithDeviceUniqueKey( + deviceUniqueKeyPair, + scratchPad, + (short) 0, + signStructure, + repository.getHeap(), + reclaimIndex); + Util.arrayCopyNonAtomic(repository.getHeap(), reclaimIndex, scratchPad, (short) 0, len); + repository.reclaimMemory(maxEcdsaSignLen); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature( + KMByteBlob.instance(scratchPad, (short) 0, len), scratchPad, (short) 0); + signStructure = KMByteBlob.instance(scratchPad, (short) 0, len); + + /* Construct unprotected headers */ + short unprotectedHeader = KMArray.instance((short) 0); + unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); + + /* construct Cose_Sign1 */ + return KMCose.constructCoseSign1( + protectedHeaders, unprotectedHeader, ephmeralMacKey, signStructure); + } + + private KMKey createDeviceUniqueKeyPair(boolean testMode, byte[] scratchPad) { + KMKey deviceUniqueKeyPair; + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + if (testMode) { + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + deviceUniqueKeyPair = + storeDataInst.createRkpTestDeviceUniqueKeyPair( + scratchPad, + (short) 128, + rkpTmpVariables[1], + scratchPad, + (short) 0, + rkpTmpVariables[0]); + } else { + deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair(false); + } + return deviceUniqueKeyPair; + } + + /** + * DeviceInfo is a CBOR Map structure described by the following CDDL. + * + *

DeviceInfo = { "brand" : tstr, "manufacturer" : tstr, "product" : tstr, "model" : tstr, + * "device" : tstr, "vb_state" : "green" / "yellow" / "orange", // Taken from the AVB values + * "bootloader_state" : "locked" / "unlocked", // Taken from the AVB values "vbmeta_digest": bstr, + * // Taken from the AVB values ? "os_version" : tstr, // Same as android.os.Build.VERSION.release + * "system_patch_level" : uint, // YYYYMMDD "boot_patch_level" : uint, //YYYYMMDD + * "vendor_patch_level" : uint, // YYYYMMDD "version" : 2, // TheCDDL schema version + * "security_level" : "tee" / "strongbox" "fused": 1 / 0, } + */ + 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, DEVICE_INFO_VERSION, KMInteger.uint_8(DI_SCHEMA_VERSION)); + 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; + } + // ---------------------------------------------------------------------------- + + // ---------------------------------------------------------------------------- + // ECDH HKDF + private short ecdhHkdfDeriveKey( + byte[] privKeyA, + short privKeyAOff, + short privKeyALen, + byte[] pubKeyA, + short pubKeyAOff, + short pubKeyALen, + byte[] pubKeyB, + short pubKeyBOff, + short pubKeyBLen, + byte[] scratchPad) { + short key = + seProvider.ecdhKeyAgreement( + privKeyA, + privKeyAOff, + privKeyALen, + pubKeyB, + pubKeyBOff, + pubKeyBLen, + scratchPad, + (short) 0); + key = KMByteBlob.instance(scratchPad, (short) 0, key); + + // ignore 0x04 for ephemerical public key as kdfContext should not include 0x04. + pubKeyAOff += 1; + pubKeyALen -= 1; + pubKeyBOff += 1; + pubKeyBLen -= 1; + short kdfContext = + KMCose.constructKdfContext( + pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, true); + kdfContext = + KMKeymasterApplet.encodeToApduBuffer( + kdfContext, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + kdfContext = KMByteBlob.instance(scratchPad, (short) 0, kdfContext); + + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 32, (byte) 0); + seProvider.hkdf( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + (short) 32, + KMByteBlob.cast(kdfContext).getBuffer(), + KMByteBlob.cast(kdfContext).getStartOff(), + KMByteBlob.cast(kdfContext).length(), + scratchPad, + (short) 32, // offset + (short) 32 // Length of expected output. + ); + Util.arrayCopy(scratchPad, (short) 32, scratchPad, (short) 0, (short) 32); + return (short) 32; + } + + // ---------------------------------------------------------------------------- + // This function returns the instance of private key and It stores the public key in the + // data table for later usage. + private short generateEphemeralEcKey(byte[] scratchPad) { + // Generate ephemeral ec key. + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + // Copy the ephemeral private key from scratch pad + short ptr = KMByteBlob.instance(rkpTmpVariables[0]); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + rkpTmpVariables[0]); + // Store ephemeral public key in data table for later usage. + short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, rkpTmpVariables[1]); + Util.arrayCopyNonAtomic(scratchPad, (short) 128, data, dataEntryIndex, rkpTmpVariables[1]); + return ptr; + } + + private void initHmacOperation() { + short dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + operation[0] = + seProvider.getRkpOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) 0, + data, + dataEntryIndex, + getEntryLength(EPHEMERAL_MAC_KEY), + null, + (short) 0, + (short) 0, + (short) 0); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private void initAesGcmOperation(byte[] scratchPad, short nonce) { + // Generate Ephemeral mac key + short privKey = generateEphemeralEcKey(scratchPad); + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // Generate session key + short eekIndex = getEntry(EEK_KEY); + // Generate session key + short sessionKeyLen = + ecdhHkdfDeriveKey( + KMByteBlob.cast(privKey).getBuffer(), /* Ephemeral Private Key */ + KMByteBlob.cast(privKey).getStartOff(), + KMByteBlob.cast(privKey).length(), + data, /* Ephemeral Public key */ + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + data, /* EEK Public key */ + eekIndex, + getEntryLength(EEK_KEY), + scratchPad /* scratchpad */); + // Initialize the Cipher object. + operation[0] = + seProvider.getRkpOperation( + KMType.ENCRYPT, + KMType.AES, + (byte) 0, + KMType.PADDING_NONE, + KMType.GCM, + scratchPad, /* key */ + (short) 0, + sessionKeyLen, + KMByteBlob.cast(nonce).getBuffer(), /* nonce */ + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + (short) (KMKeymasterApplet.AES_GCM_AUTH_TAG_LENGTH * 8)); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private short processRecipientStructure(byte[] scratchPad) { + short protectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ECDH_ES_HKDF_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeaderRecipient = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaderRecipient, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaderRecipient = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaderRecipient); + + /* Construct unprotected headers */ + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // 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), + KMType.INVALID_VALUE, + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + data, + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + KMType.INVALID_VALUE, + false); + short keyIdentifierPtr = + KMByteBlob.instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); + short unprotectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, coseKey); + + // Construct recipients structure. + return KMCose.constructRecipientsStructure( + protectedHeaderRecipient, + unprotectedHeaderRecipient, + KMSimpleValue.instance(KMSimpleValue.NULL)); + } + + private short getAdditionalCertChainProcessedLength() { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(ACC_PROCESSED_LENGTH, SHORT_SIZE); + Util.setShort(data, dataEntryIndex, (short) 0); + return (short) 0; + } + return Util.getShort(data, dataEntryIndex); + } + + private void updateAdditionalCertChainProcessedLength(short processedLen) { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + Util.setShort(data, dataEntryIndex, processedLen); + } + + private short processAdditionalCertificateChain(byte[] scratchPad) { + byte[] persistedData = storeDataInst.getAdditionalCertChain(); + short totalAccLen = Util.getShort(persistedData, (short) 0); + if (totalAccLen == 0) { + // No Additional certificate chain present. + return 0; + } + short processedLen = getAdditionalCertChainProcessedLength(); + short lengthToSend = (short) (totalAccLen - processedLen); + if (lengthToSend > MAX_SEND_DATA) { + lengthToSend = MAX_SEND_DATA; + } + short cipherTextLen = + ((KMOperation) operation[0]) + .update(persistedData, (short) (2 + processedLen), lengthToSend, scratchPad, (short) 0); + processedLen += lengthToSend; + updateAdditionalCertChainProcessedLength(processedLen); + // Update the output processing state. + updateOutputProcessingState( + (processedLen == totalAccLen) ? PROCESSING_ACC_COMPLETE : PROCESSING_ACC_IN_PROGRESS); + return cipherTextLen; + } + + // BCC for STRONGBOX has chain length of 2. So it can be returned in a single go. + private short processBcc(byte[] scratchPad) { + // Construct BCC + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : false; + short len; + if (testMode) { + short bcc = KMKeymasterApplet.generateBcc(true, scratchPad); + len = + KMKeymasterApplet.encodeToApduBuffer( + bcc, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + } else { + byte[] bcc = storeDataInst.getBootCertificateChain(); + len = Util.getShort(bcc, (short) 0); + Util.arrayCopyNonAtomic(bcc, (short) 2, scratchPad, (short) 0, len); + } + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + // move cipher text on scratch pad from starting position. + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); + // If there is no additional certificate chain present then put the state to + // PROCESSING_ACC_COMPLETE. + updateOutputProcessingState( + isAdditionalCertificateChainPresent() ? PROCESSING_BCC_COMPLETE : PROCESSING_ACC_COMPLETE); + return cipherTextLen; + } + + // AAD is the CoseEncrypt structure + private void processAesGcmUpdateAad(byte[] scratchPad) { + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + short coseEncryptStr = + KMCose.constructCoseEncryptStructure(protectedHeader, KMByteBlob.instance((short) 0)); + coseEncryptStr = + KMKeymasterApplet.encodeToApduBuffer( + coseEncryptStr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + ((KMOperation) operation[0]).updateAAD(scratchPad, (short) 0, coseEncryptStr); + } + + private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short deviceInfo) { + // Construct SignedMac + KMKey deviceUniqueKeyPair = + createDeviceUniqueKeyPair((TRUE == data[getEntry(TEST_MODE)]) ? true : false, scratchPad); + // Create signedMac + short signedMac = + createSignedMac(deviceUniqueKeyPair, scratchPad, deviceInfo, pubKeysToSignMac); + // Prepare partial data for encryption. + short arrLength = (short) (isAdditionalCertificateChainPresent() ? 3 : 2); + short arr = KMArray.instance(arrLength); + KMArray.cast(arr).add((short) 0, signedMac); + KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE); + if (arrLength == 3) { + KMArray.cast(arr).add((short) 2, KMType.INVALID_VALUE); + } + short len = + KMKeymasterApplet.encodeToApduBuffer( + arr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + return cipherTextLen; + } + + private short getCoseEncryptProtectedHeader(byte[] scratchPad) { + // CoseEncrypt protected headers. + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + return KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + } + + private short getCoseEncryptUnprotectedHeader(byte[] scratchPad, short nonce) { + /* CoseEncrypt unprotected headers */ + return KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); + } + + 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), + KMType.INVALID_VALUE, + 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(MAC_KEY_SIZE); + Util.arrayFillNonAtomic( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + MAC_KEY_SIZE, + (byte) 0); + result = + seProvider.hmacSign( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + MAC_KEY_SIZE, + data, + dataStart, + dataLength, + signature, + signatureStart); + } else { + result = + seProvider.hmacSign( + storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart); + } + return result; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java index 6165c31..07b2675 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java @@ -4,6 +4,11 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; +/** + * KMSemanticTag corresponds to CBOR type of tagged item. The structure is defined as struct{byte + * SEMANTIC_TAG_TYPE; short length; tag, short ptr }. Tag is INTEGER_TYPE and the possible values + * are defined here https://www.rfc-editor.org/rfc/rfc7049#section-2.4 + */ public class KMSemanticTag extends KMType { public static final short COSE_MAC_SEMANTIC_TAG = (short) 0x0011; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java index 14b7bb8..6dffd73 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -4,6 +4,10 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; +/** + * KMSimpleValue corresponds to CBOR type of Simple value. It holds either true, false or NULL + * values. The structure is defined as struct{byte SIMPLE_VALUE_TYPE; short length; simple value } + */ public class KMSimpleValue extends KMType { public static final byte FALSE = (byte) 20; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java deleted file mode 100644 index e33ecdf..0000000 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java +++ /dev/null @@ -1,1601 +0,0 @@ -/* - * 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.KMDeviceUniqueKeyPair; -import com.android.javacard.seprovider.KMException; -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 the remote key provisioning. Generates an RKP key and generates a certificate signing - * request(CSR). The generation of CSR is divided amoung 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 N times, where - * N is the number of keys. Similarly getResponse is called is multiple times till the client receives the - * response completely. - */ -public class RemotelyProvisionedComponentDevice { - - // Device Info labels - public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; - public static final byte[] MANUFACTURER = { - 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72 - }; - public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; - public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; - public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65}; - public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; - public static final byte[] BOOTLOADER_STATE = { - 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65 - }; - public static final byte[] VB_META_DIGEST = { - 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74 - }; - public static final byte[] OS_VERSION = { - 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E - }; - 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 - }; - public static final byte[] BOOT_PATCH_LEVEL = { - 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C - }; - 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 - }; - public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; - public static final byte[] SECURITY_LEVEL = { - 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C - }; - public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64}; - // Verified boot state values - public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; - public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; - public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; - public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; - // Boot loader state values - public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; - public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; - // Device info CDDL schema version - public static final byte DI_SCHEMA_VERSION = 2; - public static final byte[] DI_SECURITY_LEVEL = { - 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78 - }; - public static final byte DATA_INDEX_ENTRY_SIZE = 4; - public static final byte DATA_INDEX_ENTRY_OFFSET = 2; - private static final byte TRUE = 0x01; - private static final byte FALSE = 0x00; - // RKP Version - private static final short RKP_VERSION = (short) 0x02; - // Boot params - 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; - private static final boolean IS_ACC_SUPPORTED_IN_RKP_SERVER = false; - private static final short MAX_SEND_DATA = 512; - private static final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; - private static final byte[] uniqueId = { - 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, 0x20, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, - 0x74 - }; // "strongbox keymint" - // more data or no data - private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve - private static final byte NO_DATA = 0x00; - // Response processing states - private static final byte START_PROCESSING = 0x00; - private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; - private static final byte PROCESSING_BCC_COMPLETE = 0x04; - private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; // Additional certificate chain. - private static final byte PROCESSING_ACC_COMPLETE = 0x0A; - // data table - private static final short DATA_SIZE = 512; - private static final byte DATA_INDEX_SIZE = 11; - // data offsets - private static final byte EPHEMERAL_MAC_KEY = 0; - private static final byte TOTAL_KEYS_TO_SIGN = 1; - private static final byte KEYS_TO_SIGN_COUNT = 2; - private static final byte TEST_MODE = 3; - private static final byte EEK_KEY = 4; - private static final byte EEK_KEY_ID = 5; - private static final byte CHALLENGE = 6; - private static final byte GENERATE_CSR_PHASE = 7; - private static final byte EPHEMERAL_PUB_KEY = 8; - private static final byte RESPONSE_PROCESSING_STATE = 9; - private static final byte ACC_PROCESSED_LENGTH = 10; - - // data item sizes - private static final byte MAC_KEY_SIZE = 32; - private static final byte SHORT_SIZE = 2; - private static final byte BYTE_SIZE = 1; - private static final byte TEST_MODE_SIZE = 1; - // generate csr states - private static final byte BEGIN = 0x01; - private static final byte UPDATE = 0x02; - private static final byte FINISH = 0x04; - private static final byte GET_RESPONSE = 0x06; - - // RKP mac key size - private static final byte RKP_MAC_KEY_SIZE = 32; - public static Object[] authorizedEekRoots; - public short[] rkpTmpVariables; - // variables - private byte[] data; - private KMEncoder encoder; - private KMDecoder decoder; - private KMRepository repository; - private KMSEProvider seProvider; - private KMKeymintDataStore storeDataInst; - private Object[] operation; - private short[] dataIndex; - - public RemotelyProvisionedComponentDevice( - 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; - createAuthorizedEEKRoot(); - } - - private void createAuthorizedEEKRoot() { - if (authorizedEekRoots == null) { - authorizedEekRoots = - new Object[] { - new byte[] { - (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, - (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, - (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, - (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, - (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, - (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, - (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, - (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, - (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, - (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, - (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f - }, - }; - } - } - - 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 short getEntryLength(short index) { - index = (short) (index * DATA_INDEX_ENTRY_SIZE); - return Util.getShort(data, index); - } - - private void processGetRkpHwInfoCmd(APDU apdu) { - // Make the response - // Author name - Google. - short respPtr = KMArray.instance((short) 5); - 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(google, (short) 0, (short) google.length)); - resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_P256)); - resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); - 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 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, KMSimpleValue.exp()); - arr = KMKeymasterApplet.receiveIncoming(apdu, arr); - // Re-purpose the apdu buffer as scratch pad. - byte[] scratchPad = apdu.getBuffer(); - // Generate ephemeral mac key. - short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, MAC_KEY_SIZE); - seProvider.newRandomNumber(data, dataEntryIndex, MAC_KEY_SIZE); - // Initialize hmac operation. - initHmacOperation(); - // Partially encode CoseMac structure with partial payload. - constructPartialPubKeysToSignMac( - scratchPad, - KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), - KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort()); - // Store the total keys in data table. - dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); - Util.setShort( - data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); - // Store the test mode value in data table. - dataEntryIndex = createEntry(TEST_MODE, TEST_MODE_SIZE); - data[dataEntryIndex] = - (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 2)).getValue()) - ? TRUE - : FALSE; - // Store the current csr status, which is BEGIN. - createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); - updateState(BEGIN); - // Send response. - KMKeymasterApplet.sendResponse(apdu, KMError.OK); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - 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); - 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 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 Hmac update with input as encoded CoseKey. - ((KMOperation) operation[0]).update(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. - KMKeymasterApplet.sendResponse(apdu, KMError.OK); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - public void processUpdateEekChain(APDU apdu) throws Exception { - try { - // The prior state can be BEGIN or UPDATE - validateState((byte) (BEGIN | UPDATE)); - short headers = KMCoseHeaders.exp(); - short arrInst = KMArray.instance((short) 4); - 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 arrSignPtr = KMArray.exp(arrInst); - arrInst = KMKeymasterApplet.receiveIncoming(apdu, arrSignPtr); - if (KMArray.cast(arrInst).length() == 0) { - KMException.throwIt(KMError.STATUS_INVALID_EEK); - } - // Re-purpose the apdu buffer as scratch pad. - byte[] scratchPad = apdu.getBuffer(); - // Validate eek chain. - short eekKey = validateAndExtractEekPub(arrInst, scratchPad); - // Store eek public key and eek id in the data table. - short eekKeyId = KMCoseKey.cast(eekKey).getKeyIdentifier(); - short dataEntryIndex = createEntry(EEK_KEY_ID, KMByteBlob.cast(eekKeyId).length()); - Util.arrayCopyNonAtomic( - KMByteBlob.cast(eekKeyId).getBuffer(), - KMByteBlob.cast(eekKeyId).getStartOff(), - data, - dataEntryIndex, - KMByteBlob.cast(eekKeyId).length()); - // Convert the coseKey to a public key. - short len = KMCoseKey.cast(eekKey).getEcdsa256PublicKey(scratchPad, (short) 0); - dataEntryIndex = createEntry(EEK_KEY, len); - Util.arrayCopyNonAtomic(scratchPad, (short) 0, data, dataEntryIndex, len); - // Update the state - updateState(UPDATE); - KMKeymasterApplet.sendResponse(apdu, KMError.OK); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - public void processUpdateChallenge(APDU apdu) throws Exception { - try { - // The prior state can be BEGIN or UPDATE - validateState((byte) (BEGIN | UPDATE)); - short arr = KMArray.instance((short) 1); - KMArray.cast(arr).add((short) 0, KMByteBlob.exp()); - arr = KMKeymasterApplet.receiveIncoming(apdu, arr); - // Store the challenge in the data table. - short challenge = KMArray.cast(arr).get((short) 0); - short challengeLen = KMByteBlob.cast(challenge).length(); - if (challengeLen > 64) { - KMException.throwIt(KMError.INVALID_INPUT_LENGTH); - } - short dataEntryIndex = createEntry(CHALLENGE, challengeLen); - Util.arrayCopyNonAtomic( - KMByteBlob.cast(challenge).getBuffer(), - KMByteBlob.cast(challenge).getStartOff(), - data, - dataEntryIndex, - challengeLen); - // Update the state - updateState(UPDATE); - KMKeymasterApplet.sendResponse(apdu, KMError.OK); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - // This function returns pubKeysToSignMac, deviceInfo and partially constructed protected data - // wrapped inside byte blob. The partial protected data contains Headers and encrypted signedMac. - 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); - // release operation - releaseOperation(); - short pubKeysToSignMac = KMByteBlob.instance(scratchPad, (short) 0, len); - // Create DeviceInfo - short deviceInfo = createDeviceInfo(scratchPad); - // Generate Nonce for AES-GCM - seProvider.newRandomNumber(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); - short nonce = - KMByteBlob.instance(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); - // Initializes cipher instance. - initAesGcmOperation(scratchPad, nonce); - // Encode Enc_Structure as additional data for AES-GCM. - processAesGcmUpdateAad(scratchPad); - short partialPayloadLen = processSignedMac(scratchPad, pubKeysToSignMac, deviceInfo); - short partialCipherText = KMByteBlob.instance(scratchPad, (short) 0, partialPayloadLen); - short coseEncryptProtectedHeader = getCoseEncryptProtectedHeader(scratchPad); - short coseEncryptUnProtectedHeader = getCoseEncryptUnprotectedHeader(scratchPad, nonce); - len = - KMKeymasterApplet.encodeToApduBuffer( - deviceInfo, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - short encodedDeviceInfo = KMByteBlob.instance(scratchPad, (short) 0, len); - updateState(FINISH); - short arr = KMArray.instance((short) 7); - KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(arr).add((short) 1, pubKeysToSignMac); - KMArray.cast(arr).add((short) 2, encodedDeviceInfo); - KMArray.cast(arr).add((short) 3, coseEncryptProtectedHeader); - KMArray.cast(arr).add((short) 4, coseEncryptUnProtectedHeader); - KMArray.cast(arr).add((short) 5, partialCipherText); - KMArray.cast(arr).add((short) 6, KMInteger.uint_8(MORE_DATA)); - KMKeymasterApplet.sendOutgoing(apdu, arr); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - public void processGetResponse(APDU apdu) throws Exception { - try { - // The prior state should be FINISH. - validateState((byte) (FINISH | GET_RESPONSE)); - byte[] scratchPad = apdu.getBuffer(); - short len = 0; - short recipientStructure = KMArray.instance((short) 0); - byte moreData = MORE_DATA; - byte state = getCurrentOutputProcessingState(); - switch (state) { - case START_PROCESSING: - case PROCESSING_BCC_IN_PROGRESS: - len = processBcc(scratchPad); - updateState(GET_RESPONSE); - break; - case PROCESSING_BCC_COMPLETE: - case PROCESSING_ACC_IN_PROGRESS: - len = processAdditionalCertificateChain(scratchPad); - updateState(GET_RESPONSE); - break; - case PROCESSING_ACC_COMPLETE: - recipientStructure = processRecipientStructure(scratchPad); - len = processFinalData(scratchPad); - moreData = NO_DATA; - releaseOperation(); - clearDataTable(); - break; - default: - KMException.throwIt(KMError.INVALID_STATE); - } - short data = KMByteBlob.instance(scratchPad, (short) 0, len); - short arr = KMArray.instance((short) 4); - KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(arr).add((short) 1, data); - KMArray.cast(arr).add((short) 2, recipientStructure); - // represents there is more output to retrieve - KMArray.cast(arr).add((short) 3, KMInteger.uint_8(moreData)); - KMKeymasterApplet.sendOutgoing(apdu, arr); - } catch (Exception e) { - clearDataTable(); - releaseOperation(); - throw e; - } - } - - 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_UPDATE_EEK_CHAIN_CMD: - processUpdateEekChain(apdu); - break; - case KMKeymasterApplet.INS_UPDATE_CHALLENGE_CMD: - processUpdateChallenge(apdu); - break; - case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: - processFinishSendData(apdu); - break; - case KMKeymasterApplet.INS_GET_RESPONSE_CMD: - processGetResponse(apdu); - break; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); - } - } - - private boolean isAdditionalCertificateChainPresent() { - if (!IS_ACC_SUPPORTED_IN_RKP_SERVER || (TRUE == data[getEntry(TEST_MODE)])) { - // Don't include AdditionalCertificateChain in ProtectedData if either - // 1. RKP server does not support processing of X.509 Additional Certificate Chain. - // 2. Requested CSR for test mode. - return false; - } - return (storeDataInst.getAdditionalCertChainLength() == 0 ? false : true); - } - - private short processFinalData(byte[] scratchPad) { - // Call finish on AES GCM Cipher - short empty = repository.alloc((short) 0); - short len = - ((KMOperation) operation[0]) - .finish(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); - return len; - } - - 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) { - boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : 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, - KMType.INVALID_VALUE, - 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; - } - - /** - * This function validates the EEK Chain and extracts the leaf public key, which is used to - * generate shared secret using ECDH. - * - * @param eekArr EEK cert chain array pointer. - * @param scratchPad Scratch buffer used to store temp results. - * @return CoseKey instance. - */ - private short validateAndExtractEekPub(short eekArr, byte[] scratchPad) { - short leafPubKey = 0; - try { - leafPubKey = - KMKeymasterApplet.validateCertChain( - (TRUE == data[getEntry(TEST_MODE)]) ? false : true, // validate EEK root - KMCose.COSE_ALG_ES256, - KMCose.COSE_ALG_ECDH_ES_HKDF_256, - eekArr, - scratchPad, - authorizedEekRoots); - } catch (KMException e) { - KMException.throwIt(KMError.STATUS_INVALID_EEK); - } - return leafPubKey; - } - - 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; - } - - /** - * This function constructs a Mac Structure, encode it and signs the encoded buffer with the - * ephemeral mac key. - */ - private void constructPartialPubKeysToSignMac( - byte[] scratchPad, short arrayLength, short encodedCoseKeysLen) { - short ptr; - short len; - 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 - ptr = - KMCose.constructCoseMacStructure( - protectedHeader, KMByteBlob.instance((short) 0), KMType.INVALID_VALUE); - // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 - len = - KMKeymasterApplet.encodeToApduBuffer( - ptr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - // Construct partial payload - Bstr Header + Array Header - // The maximum combined length of bstr header and array header length is 6 bytes. - // The lengths will never exceed Max SHORT value. - short arrPtr = KMArray.instance(arrayLength); - for (short i = 0; i < arrayLength; i++) { - KMArray.cast(arrPtr).add(i, KMType.INVALID_VALUE); - } - arrayLength = encoder.getEncodedLength(arrPtr); - short bufIndex = repository.alloc((short) 6); - short partialPayloadLen = - encoder.encodeByteBlobHeader( - (short) (arrayLength + encodedCoseKeysLen), repository.getHeap(), bufIndex, (short) 3); - - partialPayloadLen += - encoder.encode( - arrPtr, - repository.getHeap(), - (short) (bufIndex + partialPayloadLen), - repository.getHeapReclaimIndex()); - Util.arrayCopyNonAtomic(repository.getHeap(), bufIndex, scratchPad, len, partialPayloadLen); - ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); - } - - private short createSignedMac( - KMDeviceUniqueKeyPair deviceUniqueKeyPair, - byte[] scratchPad, - short deviceMapPtr, - short pubKeysToSign) { - // Challenge - short dataEntryIndex = getEntry(CHALLENGE); - short challengePtr = KMByteBlob.instance(data, dataEntryIndex, getEntryLength(CHALLENGE)); - // Ephemeral mac key - dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); - short ephmeralMacKey = - KMByteBlob.instance(data, dataEntryIndex, getEntryLength(EPHEMERAL_MAC_KEY)); - - /* Prepare AAD */ - short aad = KMArray.instance((short) 3); - KMArray.cast(aad).add((short) 0, challengePtr); - KMArray.cast(aad).add((short) 1, deviceMapPtr); - KMArray.cast(aad).add((short) 2, pubKeysToSign); - aad = - KMKeymasterApplet.encodeToApduBuffer( - aad, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - aad = KMByteBlob.instance(scratchPad, (short) 0, aad); - - /* 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 cose sign structure */ - short signStructure = KMCose.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); - signStructure = - KMKeymasterApplet.encodeToApduBuffer( - signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - // 72 is the maximum ECDSA Signature length. - short maxEcdsaSignLen = 72; - short reclaimIndex = repository.allocReclaimableMemory(maxEcdsaSignLen); - short len = - seProvider.ecSign256( - deviceUniqueKeyPair, - scratchPad, - (short) 0, - signStructure, - repository.getHeap(), - reclaimIndex); - Util.arrayCopyNonAtomic(repository.getHeap(), reclaimIndex, scratchPad, (short) 0, len); - repository.reclaimMemory(maxEcdsaSignLen); - len = - KMAsn1Parser.instance() - .decodeEcdsa256Signature( - KMByteBlob.instance(scratchPad, (short) 0, len), scratchPad, (short) 0); - signStructure = KMByteBlob.instance(scratchPad, (short) 0, len); - - /* Construct unprotected headers */ - short unprotectedHeader = KMArray.instance((short) 0); - unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); - - /* construct Cose_Sign1 */ - return KMCose.constructCoseSign1( - protectedHeaders, unprotectedHeader, ephmeralMacKey, signStructure); - } - - private KMDeviceUniqueKeyPair createDeviceUniqueKeyPair(boolean testMode, byte[] scratchPad) { - KMDeviceUniqueKeyPair deviceUniqueKeyPair; - rkpTmpVariables[0] = 0; - rkpTmpVariables[1] = 0; - if (testMode) { - seProvider.createAsymmetricKey( - KMType.EC, - scratchPad, - (short) 0, - (short) 128, - scratchPad, - (short) 128, - (short) 128, - rkpTmpVariables); - deviceUniqueKeyPair = - storeDataInst.createRkpTestDeviceUniqueKeyPair( - scratchPad, - (short) 128, - rkpTmpVariables[1], - scratchPad, - (short) 0, - rkpTmpVariables[0]); - } else { - deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair(false); - } - return deviceUniqueKeyPair; - } - - /** - * DeviceInfo is a CBOR Map structure described by the following CDDL. - * - *

DeviceInfo = { "brand" : tstr, "manufacturer" : tstr, "product" : tstr, "model" : tstr, - * "device" : tstr, "vb_state" : "green" / "yellow" / "orange", // Taken from the AVB values - * "bootloader_state" : "locked" / "unlocked", // Taken from the AVB values "vbmeta_digest": bstr, - * // Taken from the AVB values ? "os_version" : tstr, // Same as android.os.Build.VERSION.release - * "system_patch_level" : uint, // YYYYMMDD "boot_patch_level" : uint, //YYYYMMDD - * "vendor_patch_level" : uint, // YYYYMMDD "version" : 2, // TheCDDL schema version - * "security_level" : "tee" / "strongbox" "fused": 1 / 0, } - */ - 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, DEVICE_INFO_VERSION, KMInteger.uint_8(DI_SCHEMA_VERSION)); - 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; - } - // ---------------------------------------------------------------------------- - - // ---------------------------------------------------------------------------- - // ECDH HKDF - private short ecdhHkdfDeriveKey( - byte[] privKeyA, - short privKeyAOff, - short privKeyALen, - byte[] pubKeyA, - short pubKeyAOff, - short pubKeyALen, - byte[] pubKeyB, - short pubKeyBOff, - short pubKeyBLen, - byte[] scratchPad) { - short key = - seProvider.ecdhKeyAgreement( - privKeyA, - privKeyAOff, - privKeyALen, - pubKeyB, - pubKeyBOff, - pubKeyBLen, - scratchPad, - (short) 0); - key = KMByteBlob.instance(scratchPad, (short) 0, key); - - // ignore 0x04 for ephemerical public key as kdfContext should not include 0x04. - pubKeyAOff += 1; - pubKeyALen -= 1; - pubKeyBOff += 1; - pubKeyBLen -= 1; - short kdfContext = - KMCose.constructKdfContext( - pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, true); - kdfContext = - KMKeymasterApplet.encodeToApduBuffer( - kdfContext, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - kdfContext = KMByteBlob.instance(scratchPad, (short) 0, kdfContext); - - Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 32, (byte) 0); - seProvider.hkdf( - KMByteBlob.cast(key).getBuffer(), - KMByteBlob.cast(key).getStartOff(), - KMByteBlob.cast(key).length(), - scratchPad, - (short) 0, - (short) 32, - KMByteBlob.cast(kdfContext).getBuffer(), - KMByteBlob.cast(kdfContext).getStartOff(), - KMByteBlob.cast(kdfContext).length(), - scratchPad, - (short) 32, // offset - (short) 32 // Length of expected output. - ); - Util.arrayCopy(scratchPad, (short) 32, scratchPad, (short) 0, (short) 32); - return (short) 32; - } - - // ---------------------------------------------------------------------------- - // This function returns the instance of private key and It stores the public key in the - // data table for later usage. - private short generateEphemeralEcKey(byte[] scratchPad) { - // Generate ephemeral ec key. - rkpTmpVariables[0] = 0; - rkpTmpVariables[1] = 0; - seProvider.createAsymmetricKey( - KMType.EC, - scratchPad, - (short) 0, - (short) 128, - scratchPad, - (short) 128, - (short) 128, - rkpTmpVariables); - // Copy the ephemeral private key from scratch pad - short ptr = KMByteBlob.instance(rkpTmpVariables[0]); - Util.arrayCopyNonAtomic( - scratchPad, - (short) 0, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - rkpTmpVariables[0]); - // Store ephemeral public key in data table for later usage. - short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, rkpTmpVariables[1]); - Util.arrayCopyNonAtomic(scratchPad, (short) 128, data, dataEntryIndex, rkpTmpVariables[1]); - return ptr; - } - - private void initHmacOperation() { - short dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); - operation[0] = - seProvider.getRkpOperation( - KMType.SIGN, - KMType.HMAC, - KMType.SHA2_256, - KMType.PADDING_NONE, - (byte) 0, - data, - dataEntryIndex, - getEntryLength(EPHEMERAL_MAC_KEY), - null, - (short) 0, - (short) 0, - (short) 0); - if (operation[0] == null) { - KMException.throwIt(KMError.STATUS_FAILED); - } - } - - private void initAesGcmOperation(byte[] scratchPad, short nonce) { - // Generate Ephemeral mac key - short privKey = generateEphemeralEcKey(scratchPad); - short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); - // Generate session key - short eekIndex = getEntry(EEK_KEY); - // Generate session key - short sessionKeyLen = - ecdhHkdfDeriveKey( - KMByteBlob.cast(privKey).getBuffer(), /* Ephemeral Private Key */ - KMByteBlob.cast(privKey).getStartOff(), - KMByteBlob.cast(privKey).length(), - data, /* Ephemeral Public key */ - pubKeyIndex, - getEntryLength(EPHEMERAL_PUB_KEY), - data, /* EEK Public key */ - eekIndex, - getEntryLength(EEK_KEY), - scratchPad /* scratchpad */); - // Initialize the Cipher object. - operation[0] = - seProvider.getRkpOperation( - KMType.ENCRYPT, - KMType.AES, - (byte) 0, - KMType.PADDING_NONE, - KMType.GCM, - scratchPad, /* key */ - (short) 0, - sessionKeyLen, - KMByteBlob.cast(nonce).getBuffer(), /* nonce */ - KMByteBlob.cast(nonce).getStartOff(), - KMByteBlob.cast(nonce).length(), - (short) (KMKeymasterApplet.AES_GCM_AUTH_TAG_LENGTH * 8)); - if (operation[0] == null) { - KMException.throwIt(KMError.STATUS_FAILED); - } - } - - private short processRecipientStructure(byte[] scratchPad) { - short protectedHeaderRecipient = - KMCose.constructHeaders( - rkpTmpVariables, - KMNInteger.uint_8(KMCose.COSE_ALG_ECDH_ES_HKDF_256), - KMType.INVALID_VALUE, - KMType.INVALID_VALUE, - KMType.INVALID_VALUE); - // Encode the protected header as byte blob. - protectedHeaderRecipient = - KMKeymasterApplet.encodeToApduBuffer( - protectedHeaderRecipient, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - protectedHeaderRecipient = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaderRecipient); - - /* Construct unprotected headers */ - short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); - // 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), - KMType.INVALID_VALUE, - KMInteger.uint_8(KMCose.COSE_ECCURVE_256), - data, - pubKeyIndex, - getEntryLength(EPHEMERAL_PUB_KEY), - KMType.INVALID_VALUE, - false); - short keyIdentifierPtr = - KMByteBlob.instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); - short unprotectedHeaderRecipient = - KMCose.constructHeaders( - rkpTmpVariables, KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, coseKey); - - // Construct recipients structure. - return KMCose.constructRecipientsStructure( - protectedHeaderRecipient, - unprotectedHeaderRecipient, - KMSimpleValue.instance(KMSimpleValue.NULL)); - } - - private short getAdditionalCertChainProcessedLength() { - short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); - if (dataEntryIndex == 0) { - dataEntryIndex = createEntry(ACC_PROCESSED_LENGTH, SHORT_SIZE); - Util.setShort(data, dataEntryIndex, (short) 0); - return (short) 0; - } - return Util.getShort(data, dataEntryIndex); - } - - private void updateAdditionalCertChainProcessedLength(short processedLen) { - short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); - Util.setShort(data, dataEntryIndex, processedLen); - } - - private short processAdditionalCertificateChain(byte[] scratchPad) { - byte[] persistedData = storeDataInst.getAdditionalCertChain(); - short totalAccLen = Util.getShort(persistedData, (short) 0); - if (totalAccLen == 0) { - // No Additional certificate chain present. - return 0; - } - short processedLen = getAdditionalCertChainProcessedLength(); - short lengthToSend = (short) (totalAccLen - processedLen); - if (lengthToSend > MAX_SEND_DATA) { - lengthToSend = MAX_SEND_DATA; - } - short cipherTextLen = - ((KMOperation) operation[0]) - .update(persistedData, (short) (2 + processedLen), lengthToSend, scratchPad, (short) 0); - processedLen += lengthToSend; - updateAdditionalCertChainProcessedLength(processedLen); - // Update the output processing state. - updateOutputProcessingState( - (processedLen == totalAccLen) ? PROCESSING_ACC_COMPLETE : PROCESSING_ACC_IN_PROGRESS); - return cipherTextLen; - } - - // BCC for STRONGBOX has chain length of 2. So it can be returned in a single go. - private short processBcc(byte[] scratchPad) { - // Construct BCC - boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : false; - short len; - if (testMode) { - short bcc = KMKeymasterApplet.generateBcc(true, scratchPad); - len = - KMKeymasterApplet.encodeToApduBuffer( - bcc, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - } else { - byte[] bcc = storeDataInst.getBootCertificateChain(); - len = Util.getShort(bcc, (short) 0); - Util.arrayCopyNonAtomic(bcc, (short) 2, scratchPad, (short) 0, len); - } - short cipherTextLen = - ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); - // move cipher text on scratch pad from starting position. - Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); - createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); - // If there is no additional certificate chain present then put the state to - // PROCESSING_ACC_COMPLETE. - updateOutputProcessingState( - isAdditionalCertificateChainPresent() ? PROCESSING_BCC_COMPLETE : PROCESSING_ACC_COMPLETE); - return cipherTextLen; - } - - // AAD is the CoseEncrypt structure - private void processAesGcmUpdateAad(byte[] scratchPad) { - short protectedHeader = - KMCose.constructHeaders( - rkpTmpVariables, - KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), - KMType.INVALID_VALUE, - KMType.INVALID_VALUE, - KMType.INVALID_VALUE); - // Encode the protected header as byte blob. - protectedHeader = - KMKeymasterApplet.encodeToApduBuffer( - protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); - short coseEncryptStr = - KMCose.constructCoseEncryptStructure(protectedHeader, KMByteBlob.instance((short) 0)); - coseEncryptStr = - KMKeymasterApplet.encodeToApduBuffer( - coseEncryptStr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - ((KMOperation) operation[0]).updateAAD(scratchPad, (short) 0, coseEncryptStr); - } - - private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short deviceInfo) { - // Construct SignedMac - KMDeviceUniqueKeyPair deviceUniqueKeyPair = - createDeviceUniqueKeyPair((TRUE == data[getEntry(TEST_MODE)]) ? true : false, scratchPad); - // Create signedMac - short signedMac = - createSignedMac(deviceUniqueKeyPair, scratchPad, deviceInfo, pubKeysToSignMac); - // Prepare partial data for encryption. - short arrLength = (short) (isAdditionalCertificateChainPresent() ? 3 : 2); - short arr = KMArray.instance(arrLength); - KMArray.cast(arr).add((short) 0, signedMac); - KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE); - if (arrLength == 3) { - KMArray.cast(arr).add((short) 2, KMType.INVALID_VALUE); - } - short len = - KMKeymasterApplet.encodeToApduBuffer( - arr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - short cipherTextLen = - ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); - Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); - return cipherTextLen; - } - - private short getCoseEncryptProtectedHeader(byte[] scratchPad) { - // CoseEncrypt protected headers. - short protectedHeader = - KMCose.constructHeaders( - rkpTmpVariables, - KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), - KMType.INVALID_VALUE, - KMType.INVALID_VALUE, - KMType.INVALID_VALUE); - // Encode the protected header as byte blob. - protectedHeader = - KMKeymasterApplet.encodeToApduBuffer( - protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); - return KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); - } - - private short getCoseEncryptUnprotectedHeader(byte[] scratchPad, short nonce) { - /* CoseEncrypt unprotected headers */ - return KMCose.constructHeaders( - rkpTmpVariables, KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); - } - - 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), - KMType.INVALID_VALUE, - 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(MAC_KEY_SIZE); - Util.arrayFillNonAtomic( - KMByteBlob.cast(macKey).getBuffer(), - KMByteBlob.cast(macKey).getStartOff(), - MAC_KEY_SIZE, - (byte) 0); - result = - seProvider.hmacSign( - KMByteBlob.cast(macKey).getBuffer(), - KMByteBlob.cast(macKey).getStartOff(), - MAC_KEY_SIZE, - data, - dataStart, - dataLength, - signature, - signatureStart); - } else { - result = - seProvider.hmacSign( - storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart); - } - return result; - } -} -- cgit v1.2.3 From 9ea276d7ad432d10d1dcf4eea898b70642e6b08c Mon Sep 17 00:00:00 2001 From: subrahmanyaman Date: Fri, 30 Dec 2022 06:23:36 +0000 Subject: km200: Added documentation as per the review comments on aosp/2082578 Added documentation, specifying the purpose and meaning of each group of constants. Bug: b/242702664 Test: run vts -m VtsAidlKeyMintTarget Change-Id: I94e4af0302c9e46e08e6c61baaae12d4080fdbdb --- .../javacard/keymaster/KMAndroidSEApplet.java | 13 +- .../javacard/keymaster/KMAttestationCertImpl.java | 16 +- .../javacard/seprovider/KMAndroidSEProvider.java | 26 ++- .../android/javacard/keymaster/KMAsn1Parser.java | 6 + .../javacard/keymaster/KMKeymasterApplet.java | 86 ++++++-- .../javacard/keymaster/KMKeymintDataStore.java | 1 - .../KMRemotelyProvisionedComponentDevice.java | 217 ++++++++++++++++++--- .../android/javacard/keymaster/KMRepository.java | 5 +- 8 files changed, 304 insertions(+), 66 deletions(-) diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java index 146b634..b356643 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -33,13 +33,12 @@ import org.globalplatform.upgrade.UpgradeManager; * class which stores the data in the flash memory. */ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { - // Magic number version + // Magic number version stored along with provisioned data. This is used to differentiate + // between data before and after the magic number is used. private static final byte KM_MAGIC_NUMBER = (byte) 0x82; // MSB byte is for Major version and LSB byte is for Minor version. public static final short KM_APPLET_PACKAGE_VERSION = 0x0301; - - private static final byte KM_BEGIN_STATE = 0x00; - private static final byte ILLEGAL_STATE = KM_BEGIN_STATE + 1; + // This flag is used to know if card reset happened. private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; // Provider specific Commands @@ -67,11 +66,11 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis INS_KEYMINT_PROVIDER_APDU_START + 18; private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; - public static final byte BOOT_KEY_MAX_SIZE = 32; - public static final byte BOOT_HASH_MAX_SIZE = 32; + // The length of the provisioned pre shared key. public static final byte SHARED_SECRET_KEY_SIZE = 32; - // Package version. + // Version of the database which is used to differentiate between different version of the + // database. protected short packageVersion; KMAndroidSEApplet() { diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java index 7245793..e7d8252 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -31,6 +31,7 @@ import javacard.framework.Util; */ public class KMAttestationCertImpl implements KMAttestationCert { + // The maximum size of the either software or hardware parameters. private static final byte MAX_PARAMS = 30; // DER encoded object identifiers required by the cert. // rsaEncryption - 1.2.840.113549.1.1.1 @@ -51,7 +52,9 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static final byte[] androidExtn = { 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 }; + // The length of the RSA signature. private static final short RSA_SIG_LEN = 256; + // The maximum length of the ECDSA signature. private static final byte ECDSA_MAX_SIG_LEN = 72; // Signature algorithm identifier - ecdsaWithSha256 - 1.2.840.10045.4.3.2 // SEQUENCE of alg OBJ ID and parameters = NULL. @@ -123,16 +126,17 @@ public class KMAttestationCertImpl implements KMAttestationCert { KMType.ALGORITHM, KMType.PURPOSE }; - + // Below are the constants for the key usage extension. private static final byte keyUsageSign = (byte) 0x80; // 0 bit private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit private static final byte keyUsageKeyAgreement = (byte) 0x08; // 4th- bit private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit - + // KeyMint HAL Version constant. private static final short KEYMINT_VERSION = 200; + // Attestation version constant. private static final short ATTESTATION_VERSION = 200; - private static final byte[] pubExponent = {0x01, 0x00, 0x01}; + // The X.509 version as per rfc5280#section-4.1.2.1 private static final byte X509_VERSION = (byte) 0x02; // Buffer indexes in transient array @@ -178,7 +182,7 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static byte[] stack; private static short[] swParams; private static short[] hwParams; - + // The maximum size of the serial number. private static final byte SERIAL_NUM_MAX_LEN = 20; private KMAttestationCertImpl() {} @@ -436,8 +440,8 @@ public class KMAttestationCertImpl implements KMAttestationCert { // as positive integer} private static void pushRsaSubjectKeyInfo() { short last = indexes[STACK_PTR]; - pushBytes(pubExponent, (short) 0, (short) pubExponent.length); - pushIntegerHeader((short) pubExponent.length); + pushBytes(KMKeymasterApplet.F4, (short) 0, (short) KMKeymasterApplet.F4.length); + pushIntegerHeader((short) KMKeymasterApplet.F4.length); pushBytes( KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java index 6bfc8c3..cb33272 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -47,20 +47,28 @@ import org.globalplatform.upgrade.UpgradeManager; */ public class KMAndroidSEProvider implements KMSEProvider { + // The tag length for AES GCM algorithm. public static final byte AES_GCM_TAG_LENGTH = 16; + // The nonce length for AES GCM algorithm. public static final byte AES_GCM_NONCE_LENGTH = 12; + // AES keysize offsets in aesKeys[] for 128 and 256 sizes respectively. public static final byte KEYSIZE_128_OFFSET = 0x00; public static final byte KEYSIZE_256_OFFSET = 0x01; + // The size of the temporary buffer. public static final short TMP_ARRAY_SIZE = 300; + // The length of the rsa key in bytes. private static final short RSA_KEY_SIZE = 256; - public static final short CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. - public static final byte SHARED_SECRET_KEY_SIZE = 32; + // Below are the flag to denote device reset events public static final byte POWER_RESET_FALSE = (byte) 0xAA; public static final byte POWER_RESET_TRUE = (byte) 0x00; + // The computed HMAC key size. private static final byte COMPUTED_HMAC_KEY_SIZE = 32; + // The constant 'L' as defiend in + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf, page 12. private static byte[] CMAC_KDF_CONSTANT_L; + // Constant to represent 0. private static byte[] CMAC_KDF_CONSTANT_ZERO; - + // KeyAgreement instance. private static KeyAgreement keyAgreement; // AESKey @@ -77,19 +85,21 @@ public class KMAndroidSEProvider implements KMSEProvider { public byte[] tmpArray; // This is used for internal encryption/decryption operations. private static AEADCipher aesGcmCipher; - + // Instance of Signature algorithm used in KDF. private Signature kdf; + // Flag used to denote the power reset event. public static byte[] resetFlag; - + // Instance of HMAC Signature algorithm. private Signature hmacSignature; // For ImportwrappedKey operations. private KMRsaOAEPEncoding rsaOaepDecipher; + // Instance of pool manager. private KMPoolManager poolMgr; - + // Instance of KMOperationImpl used only to encrypt/decrypt the KeyBlobs. private KMOperationImpl globalOperation; // Entropy private RandomData rng; - + // Singleton instance. private static KMAndroidSEProvider androidSEProvider = null; public static KMAndroidSEProvider getInstance() { @@ -491,6 +501,8 @@ public class KMAndroidSEProvider implements KMSEProvider { byte[] context, short contextStart, short contextLength) { + // Note: the variables i and L correspond to i and L in the standard. See page 12 of + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf. try { // This is hardcoded to requirement - 32 byte output with two concatenated // 16 bytes K1 and K2. diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java index b3eacd6..22a16a3 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -10,6 +10,7 @@ import javacard.framework.Util; */ public class KMAsn1Parser { + // Below are the ASN.1 tag types public static final byte ASN1_OCTET_STRING = 0x04; public static final byte ASN1_SEQUENCE = 0x30; public static final byte ASN1_SET = 0x31; @@ -25,9 +26,11 @@ public class KMAsn1Parser { public static final byte ASN1_UNIVERSAL_STRING = 0x1C; public static final byte ASN1_BMP_STRING = 0x1E; public static final byte IA5_STRING = 0x16; + // OID of the EC P256 curve 1.2.840.10045.3.1.7 public static final byte[] EC_CURVE = { 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 }; + // Constant for rsaEncryption pkcs#1 (1.2.840.113549.1.1.1) and NULL public static final byte[] RSA_ALGORITHM = { 0x06, 0x09, @@ -43,6 +46,7 @@ public class KMAsn1Parser { 0x05, 0x00 }; + // Constant for ecPublicKey (1.2.840.10045.2.1) and prime256v1 (1.2.840.10045.3.1.7) public static final byte[] EC_ALGORITHM = { 0x06, 0x07, @@ -64,7 +68,9 @@ public class KMAsn1Parser { 0x01, 0x07 }; + // The maximum length of email id attribute. public static final short MAX_EMAIL_ADD_LEN = 255; + // Datatable offsets. private static final byte DATA_START_OFFSET = 0; private static final byte DATA_LENGTH_OFFSET = 1; private static final byte DATA_CURSOR_OFFSET = 2; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index adc060f..427634c 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -40,42 +40,55 @@ import javacardx.apdu.ExtendedLength; 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; - public static final byte BOOT_PATCH_LVL_SIZE = 4; + // 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 }; - // "KeymasterSharedMac" + // 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 }; - // "Auth Verification" + // 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 }; - // "confirmation token" + // 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) 1024; // Maximum allowed buffer size for to encode the key parameters - // which is used while creating mac for key paramters. + // which is used while creating mac for key parameters. public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K - // Data Dictionary items + // 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; @@ -115,8 +128,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final byte CONFIRMATION_TOKEN = 36; public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37; public static final byte CUSTOM_TAGS = 38; - public static final byte DATA_ARRAY_SIZE = 39; - // Keyblob offsets. + // 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; @@ -124,8 +136,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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 constants + // 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 @@ -134,21 +147,29 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final short KEYBLOB_CURRENT_VERSION = 3; // KeyBlob Verion 1 constant. public static final short KEYBLOB_VERSION_1 = 1; - // KeyBlob array size constants. + // 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 short MIN_HMAC_LENGTH_BITS = 64; - // Provision reporting status + // 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; @@ -161,20 +182,29 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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) 0x5000; // 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 }; - // AddRngEntropy + // 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 short MIN_GCM_TAG_LENGTH_BITS = (short) 96; @@ -186,20 +216,28 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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, }; - private static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 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, @@ -210,8 +248,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe KMType.ATTESTATION_ID_PRODUCT, KMType.ATTESTATION_ID_SERIAL }; - private static final byte OEM_LOCK = 1; - private static final byte OEM_UNLOCK = 0; + // 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 @@ -261,19 +298,32 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // 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; - // ComputeHMAC constants + // 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; /** Registers this applet. */ @@ -4660,7 +4710,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // 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. - // Device unique attestation not supported short heapStart = repository.getHeapIndex(); KMTag.assertAbsence( data[KEY_PARAMETERS], @@ -4688,6 +4737,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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: @@ -4814,6 +4864,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe } } + // 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 = @@ -4845,6 +4896,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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] = diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java index 28bb810..56d99a0 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -68,7 +68,6 @@ public class KMKeymintDataStore implements KMUpgradable { private static final byte DEVICE_STATUS_FLAG_SIZE = 1; private static final short ADDITIONAL_CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. private static final short BCC_MAX_SIZE = 512; - private static final byte[] zero = {0, 0, 0, 0, 0, 0, 0, 0}; private static KMKeymintDataStore kmDataStore; // Secure Boot Mode public byte secureBootMode; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java index b844ce6..ab28cd5 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java @@ -36,84 +36,121 @@ import javacard.framework.Util; */ public class KMRemotelyProvisionedComponentDevice { - // Device Info labels + // 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}; - // Verified boot state values + // 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}; - // Boot loader state values + // 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 }; + // 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 Version + // RKP hardware info Version private static final short RKP_VERSION = (short) 0x02; - // 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 Additional certificate chain is supported in the + // RKP server. private static final boolean IS_ACC_SUPPORTED_IN_RKP_SERVER = false; + // The maximum possible output buffer. private static final short MAX_SEND_DATA = 512; - private static final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // The string "Google Strongbox KeyMint 2" in hex. private static final byte[] uniqueId = { - 0x73, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, 0x20, 0x6b, 0x65, 0x79, 0x6d, 0x69, 0x6e, - 0x74 - }; // "strongbox keymint" - // more data or no data - private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve + 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, 0x32 + }; + // 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; - // Response processing states + // Below are the response processing states. As the protected data response is huge it is + // sent back incrementally and the clients are responsible to call get response multiple times + // based on MORE_DATA or NO_DATA flags. BCC - Boot Certificate Chain. + // ACC - Additional Certificate Chain. private static final byte START_PROCESSING = 0x00; private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; private static final byte PROCESSING_BCC_COMPLETE = 0x04; - private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; // Additional certificate chain. + private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; private static final byte PROCESSING_ACC_COMPLETE = 0x0A; - // data table + // 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 = 11; - // data offsets + // Below are the data table offsets. private static final byte EPHEMERAL_MAC_KEY = 0; private static final byte TOTAL_KEYS_TO_SIGN = 1; private static final byte KEYS_TO_SIGN_COUNT = 2; @@ -126,12 +163,21 @@ public class KMRemotelyProvisionedComponentDevice { private static final byte RESPONSE_PROCESSING_STATE = 9; private static final byte ACC_PROCESSED_LENGTH = 10; - // data item sizes - private static final byte MAC_KEY_SIZE = 32; + // 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; + // The size of the test mode flag. private static final byte TEST_MODE_SIZE = 1; - // generate csr states + // 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_RESPONSE - Called multiple times by the client till all the protected data is received. private static final byte BEGIN = 0x01; private static final byte UPDATE = 0x02; private static final byte FINISH = 0x04; @@ -139,16 +185,26 @@ public class KMRemotelyProvisionedComponentDevice { // RKP mac key size private static final byte RKP_MAC_KEY_SIZE = 32; + // This holds the Google ECDSA P256 root key for the Endpoint Encryption Key. public static Object[] authorizedEekRoots; + // Used to hold the temporary results. public short[] rkpTmpVariables; - // variables + // 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( @@ -250,7 +306,10 @@ public class KMRemotelyProvisionedComponentDevice { 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(google, (short) 0, (short) google.length)); + resp.add( + (short) 2, + KMByteBlob.instance( + KMKeymasterApplet.Google, (short) 0, (short) KMKeymasterApplet.Google.length)); resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_P256)); resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); KMKeymasterApplet.sendOutgoing(apdu, respPtr); @@ -280,6 +339,25 @@ public class KMRemotelyProvisionedComponentDevice { KMKeymasterApplet.sendOutgoing(apdu, arr); } + /** + * This is the first command of the generateCSR. + * Input: + * 1) Number of RKP keys. + * 2) Total length of the encoded CoseKeys (Each RKP key is represented in CoseKey) + * 3) Flag which represents Test mode or Production mode. + * Process: + * 1) Generate Ephemeral mac key and store in the temporary data buffer. This key is + * used to sign Mac_Structure, which contains array of encoded RKP keys, + * 2) Initialize the HMAC operation with the ephemeral mac key and do partial sign of the + * Mac_Structure with the input initial data received. A Multipart update on HMAC is + * called on each updateKey command in the second stage. + * 3) Store the number of RKP keys and the test mode flag in the temporary data buffer. + * 4) Update the phase of the generateCSR function to BEGIN. + * Response: + * Send OK response. + * + * @param apdu Input apdu + */ public void processBeginSendData(APDU apdu) throws Exception { try { initializeDataTable(); @@ -291,8 +369,8 @@ public class KMRemotelyProvisionedComponentDevice { // Re-purpose the apdu buffer as scratch pad. byte[] scratchPad = apdu.getBuffer(); // Generate ephemeral mac key. - short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, MAC_KEY_SIZE); - seProvider.newRandomNumber(data, dataEntryIndex, MAC_KEY_SIZE); + short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, EPHEMERAL_MAC_KEY_SIZE); + seProvider.newRandomNumber(data, dataEntryIndex, EPHEMERAL_MAC_KEY_SIZE); // Initialize hmac operation. initHmacOperation(); // Partially encode CoseMac structure with partial payload. @@ -322,6 +400,21 @@ public class KMRemotelyProvisionedComponentDevice { } } + /** + * This is the second command of the generateCSR. + * 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 first command. + * 3) Validate the CoseMac0 structure and extract the RKP Key. + * 4) Do Multipart HMAC 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: + * Send OK response. + * @param apdu Input apdu + */ public void processUpdateKey(APDU apdu) throws Exception { try { // The prior state can be BEGIN or UPDATE @@ -366,6 +459,19 @@ public class KMRemotelyProvisionedComponentDevice { } } + /** + * This is the third command of generateCSR. + * Input: + * EEK chain ordered from Root to Leaf in CoseSign1 format. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be UPDATE. + * 2) Validate the EEK chain and extract the Leaf EEK + * 3) Retrieve the EEK Id and Public key of Leaf EEK and store in the data buffer. + * 4) Update the phase of the generateCSR function to UPDATE. + * Response: + * Send OK response. + * @param apdu Input apdu. + */ public void processUpdateEekChain(APDU apdu) throws Exception { try { // The prior state can be BEGIN or UPDATE @@ -408,6 +514,18 @@ public class KMRemotelyProvisionedComponentDevice { } } + /** + * This is the fourth command of generateCSR. + * Input: + * Challenge + * Process: + * 1) Validate the phase of generateCSR. Prior state should be either UPDATE or BEGIN. + * 2) Store the challenge in the data buffer. + * 3) Update the phase of the generateCSR function to UPDATE. + * Response: + * Send OK response. + * @param apdu Input apdu. + */ public void processUpdateChallenge(APDU apdu) throws Exception { try { // The prior state can be BEGIN or UPDATE @@ -438,8 +556,33 @@ public class KMRemotelyProvisionedComponentDevice { } } - // This function returns pubKeysToSignMac, deviceInfo and partially constructed protected data - // wrapped inside byte blob. The partial protected data contains Headers and encrypted signedMac. + /** + * This is the fifth 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 HMAC operation and get the signed Mac_Structure which is called as + * pubKeysToSignMac. + * 4) Start constructing the partial protected data. Create a random + * nonce, initialize the AESGCM operation with the session key derived from + * HKDF(ECDH(EPHEMERAL_EC_KEY, EEK_KEY), KdfContext) + * 5) Construct Encrypt_Structure which acts as AAD for AES-GCM operation. + * 6) The payload for the Protected Data is [SignedMac, BCC, ACC]. Construct partial + * SignedMac structure. + * 7) Note that the HAL has to construct the CoseEncrypt structure by collecting all + * the pieces returned from Applet. + * Response: + * OK + * pubKeysToSignMac - Containes the maced RKP public keys. + * deviceInfo - CBOR encoded device info + * protectedHeader - CoseEncrypt protected header + * unProtectedHeader - CoseEncrypt unprotected header. + * ParitalCipherText - partial encrypted payload of CoseEncrypt structure. + * 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. @@ -492,6 +635,26 @@ public class KMRemotelyProvisionedComponentDevice { } } + /** + * This is the sixth and the last command of generateCSR. This command is called multiple + * times by the HAL until all the cipher data and receipient structure is received. + * Input: + * No input data. + * Process: + * First the BootCertificateChain is processed: Encrypt the boot certificate chain and return + * the BCC. Mark the state as PROCESSING_BCC_COMPLETE. + * Next the AdditionalCertificateChain is processed: Incrementally encrypt ACC and send back + * a chunks of data. Each chunk is 512 bytes. if the processing of ACC is still in progress + * mark the state as PROCESSING_ACC_IN_PROGRESS otherwise mark the state as + * PROCESSING_ACC_COMPLETE. + * Finally construct and return the CoseReceipient structure. + * Response: + * OK + * cipher text: It can be either encrypted bcc or encrypted acc + * Receipient structure: This will be empty will returning the encrypted acc or bcc. + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ public void processGetResponse(APDU apdu) throws Exception { try { // The prior state should be FINISH. @@ -1572,17 +1735,17 @@ public class KMRemotelyProvisionedComponentDevice { short signatureStart) { short result; if (testMode) { - short macKey = KMByteBlob.instance(MAC_KEY_SIZE); + short macKey = KMByteBlob.instance(EPHEMERAL_MAC_KEY_SIZE); Util.arrayFillNonAtomic( KMByteBlob.cast(macKey).getBuffer(), KMByteBlob.cast(macKey).getStartOff(), - MAC_KEY_SIZE, + EPHEMERAL_MAC_KEY_SIZE, (byte) 0); result = seProvider.hmacSign( KMByteBlob.cast(macKey).getBuffer(), KMByteBlob.cast(macKey).getStartOff(), - MAC_KEY_SIZE, + EPHEMERAL_MAC_KEY_SIZE, data, dataStart, dataLength, diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java index dec52eb..9fd2406 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -27,12 +27,15 @@ import javacard.framework.Util; */ public class KMRepository { + // The maximum available heap memory. public static final short HEAP_SIZE = 10000; + // Index pointing from the back of heap. private static short[] reclaimIndex; // Singleton instance private static KMRepository repository; - // Class Attributes + // Heap buffer private byte[] heap; + // Index to the heap buffer. private short[] heapIndex; public KMRepository(boolean isUpgrading) { -- cgit v1.2.3 From 0b90f56a0ab0c3a8ea8b831221cd7571b367b603 Mon Sep 17 00:00:00 2001 From: avinashhedage Date: Wed, 18 Jan 2023 13:30:19 +0000 Subject: Removed km_ops from CoseKey Test: run vts -m VtsAidlKeyMintTarget Change-Id: If79c220b14c845eafa1588670e182cbf84cd2441 --- .../src/com/android/javacard/keymaster/KMCose.java | 24 ++++++++-------------- .../com/android/javacard/keymaster/KMCoseKey.java | 18 ++++++++-------- .../javacard/keymaster/KMCosePairCoseKeyTag.java | 2 +- .../javacard/keymaster/KMCosePairTagType.java | 20 +++++------------- .../com/android/javacard/keymaster/KMDecoder.java | 4 +++- .../javacard/keymaster/KMKeymasterApplet.java | 2 -- .../KMRemotelyProvisionedComponentDevice.java | 3 --- 7 files changed, 28 insertions(+), 45 deletions(-) diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java index 1eb8816..0c2244c 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -71,7 +71,6 @@ public class KMCose { public static final short COSE_KEY_KEY_TYPE = 1; public static final short COSE_KEY_KEY_ID = 2; public static final short COSE_KEY_ALGORITHM = 3; - public static final short COSE_KEY_KEY_OPS = 4; public static final short COSE_KEY_CURVE = -1; public static final short COSE_KEY_PUBKEY_X = -2; public static final short COSE_KEY_PUBKEY_Y = -3; @@ -112,7 +111,6 @@ public class KMCose { KMCose.COSE_KEY_KEY_TYPE, KMCose.COSE_KEY_KEY_ID, KMCose.COSE_KEY_ALGORITHM, - KMCose.COSE_KEY_KEY_OPS, KMCose.COSE_KEY_CURVE, KMCose.COSE_KEY_PUBKEY_X, KMCose.COSE_KEY_PUBKEY_Y, @@ -452,12 +450,13 @@ public class KMCose { } /** - * Constructs a CoseKey with the provided input paramters. + * Constructs a CoseKey with the provided input parameters. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. * * @param keyType Instance of the identification of the key type. * @param keyId Instance of key identification value. * @param keyAlg Instance of the algorithm that is used with this key. - * @param keyOps Instance of the operation that this key is used for. * @param curve Instance of the EC curve that is used with this key. * @param pubKey Buffer containing the public key. * @param pubKeyOff Start offset of the buffer. @@ -471,7 +470,6 @@ public class KMCose { short keyType, short keyId, short keyAlg, - short keyOps, short curve, byte[] pubKey, short pubKeyOff, @@ -486,8 +484,7 @@ public class KMCose { short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); short coseKey = - constructCoseKey( - buff, keyType, keyId, keyAlg, keyOps, curve, xPtr, yPtr, privKeyPtr, testMode); + constructCoseKey(buff, keyType, keyId, keyAlg, curve, xPtr, yPtr, privKeyPtr, testMode); KMCoseKey.cast(coseKey).canonicalize(); return coseKey; } @@ -499,7 +496,6 @@ public class KMCose { * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. * @param keyId instance of KMByteBlob which holds key identifier value. * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. - * @param keyOps instance of KMInteger/KMNInteger which holds valid COSE key operations. * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. * @param pubX instance of KMByteBlob which holds EC public key's x value. * @param pubY instance of KMByteBlob which holds EC public key's y value. @@ -512,21 +508,19 @@ public class KMCose { short keyType, short keyId, short keyAlg, - short keyOps, short curve, short pubX, short pubY, short priv, boolean includeTestKey) { - short valueIndex = 8; + short valueIndex = 7; buff[0] = keyType; buff[1] = keyId; buff[2] = keyAlg; - buff[3] = keyOps; - buff[4] = curve; - buff[5] = pubX; - buff[6] = pubY; - buff[7] = priv; + buff[3] = curve; + buff[4] = pubX; + buff[5] = pubY; + buff[6] = priv; for (short i = valueIndex; i < 16; i++) { buff[i] = KMType.INVALID_VALUE; } diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java index d1a9f36..d3edc5f 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -25,7 +25,9 @@ import javacard.framework.Util; * https://datatracker.ietf.org/doc/html/rfc8152#section-7 The supported key types are KMNInteger, * KMInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMSimpleValue. It * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where - * arrayPtr is a pointer to array with any KMTag subtype instances. + * arrayPtr is a pointer to array with any KMTag subtype instances. Note that construction of the + * key_ops label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. */ public class KMCoseKey extends KMCoseMap { @@ -182,7 +184,9 @@ public class KMCoseKey extends KMCoseMap { } /** - * Verifies the KMCoseKey values against the input values. + * Verifies the KMCoseKey values against the input values. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. * * @param keyType value of the key type * @param keyIdPtr instance of KMByteBlob containing the key id. @@ -192,18 +196,16 @@ public class KMCoseKey extends KMCoseMap { * @return true if valid, otherwise false. */ public boolean isDataValid( - short[] buff, short keyType, short keyIdPtr, short keyAlg, short keyOps, short curve) { - short buffLen = 10; + short[] buff, short keyType, short keyIdPtr, short keyAlg, short curve) { + short buffLen = 8; buff[0] = KMCose.COSE_KEY_KEY_TYPE; buff[1] = keyType; buff[2] = KMCose.COSE_KEY_KEY_ID; buff[3] = keyIdPtr; buff[4] = KMCose.COSE_KEY_ALGORITHM; buff[5] = keyAlg; - buff[6] = KMCose.COSE_KEY_KEY_OPS; - buff[7] = keyOps; - buff[8] = KMCose.COSE_KEY_CURVE; - buff[9] = curve; + buff[6] = KMCose.COSE_KEY_CURVE; + buff[7] = curve; boolean valid = false; short ptr; short tagIndex = 0; diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java index be853de..5290da2 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java @@ -6,7 +6,7 @@ import javacard.framework.Util; /** * KMCosePairCoseKeyTag represents a key-value type, where key can be KMInteger or KMNInteger and - * value is KMCOseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; + * value is KMCoseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; * short key; short value}}. */ public class KMCosePairCoseKeyTag extends KMCosePairTagType { diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java index f3fc76e..baa0855 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -21,12 +21,11 @@ import javacard.framework.ISOException; import javacard.framework.Util; /** - * This class represents the COSE_Key as defined in - * https://datatracker.ietf.org/doc/html/rfc8152#section-7. This is basically a map containing key - * value pairs. The label for the key can be (uint / int / tstr) and the value can be of any type. - * But this class is confined to support only key and value types which are required for remote key - * provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr) only - * are supported. The structure representing all the sub classes of KMCosePairTagType is as follows: + * This class represents the a key-value types. This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * The structure representing all the sub classes of KMCosePairTagType is as follows: * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), * Value(2 bytes). Key can be either KMInteger or KMNInteger and Value can be either KMIntger or * KMNinteger or KMSimpleValue or KMByteBlob or KMTextString or KMCoseKey. Each subclass of @@ -57,15 +56,6 @@ public abstract class KMCosePairTagType extends KMType { KMCose.COSE_ALG_ECDH_ES_HKDF_256, KMCose.COSE_ALG_ES256 }, - // Key operations - (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_OPS}, - (Object) - new byte[] { - KMCose.COSE_KEY_OP_SIGN, - KMCose.COSE_KEY_OP_VERIFY, - KMCose.COSE_KEY_OP_ENCRYPT, - KMCose.COSE_KEY_OP_DECRYPT - }, // Key Curve (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_CURVE}, (Object) new byte[] {KMCose.COSE_ECCURVE_256}, diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java index e7dc21d..dd05b13 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -240,7 +240,9 @@ public class KMDecoder { private short peekCosePairTagType() { byte[] buffer = (byte[]) bufferRef[0]; short startOff = scratchBuf[START_OFFSET]; - // Cose Key should be always either UINT or Negative int + // This decoder is confined to support only key and value types which are required for remote + // key provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr + // / tstr / Cosekey) only are supported. if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE && (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 427634c..eac55a9 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -1047,7 +1047,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe KMCose.COSE_KEY_TYPE_EC2, KMType.INVALID_VALUE, alg, - KMType.INVALID_VALUE, KMCose.COSE_ECCURVE_256)) { KMException.throwIt(KMError.STATUS_FAILED); } @@ -1127,7 +1126,6 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), KMType.INVALID_VALUE, KMNInteger.uint_8(KMCose.COSE_ALG_ES256), - KMInteger.uint_8(KMCose.COSE_KEY_OP_VERIFY), KMInteger.uint_8(KMCose.COSE_ECCURVE_256), scratchPad, (short) 0, diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java index ab28cd5..8ba0e2f 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java @@ -806,7 +806,6 @@ public class KMRemotelyProvisionedComponentDevice { KMCose.COSE_KEY_TYPE_EC2, KMType.INVALID_VALUE, KMCose.COSE_ALG_ES256, - KMType.INVALID_VALUE, KMCose.COSE_ECCURVE_256)) { KMException.throwIt(KMError.STATUS_FAILED); } @@ -1423,7 +1422,6 @@ public class KMRemotelyProvisionedComponentDevice { KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), KMType.INVALID_VALUE, KMNInteger.uint_8(KMCose.COSE_ALG_ES256), - KMType.INVALID_VALUE, KMInteger.uint_8(KMCose.COSE_ECCURVE_256), data, pubKeyIndex, @@ -1584,7 +1582,6 @@ public class KMRemotelyProvisionedComponentDevice { KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), KMType.INVALID_VALUE, KMNInteger.uint_8(KMCose.COSE_ALG_ES256), - KMType.INVALID_VALUE, KMInteger.uint_8(KMCose.COSE_ECCURVE_256), KMByteBlob.cast(pubKey).getBuffer(), KMByteBlob.cast(pubKey).getStartOff(), -- cgit v1.2.3 From e78d87ea8d5991c639882eb0a3a50e1d096c3ee0 Mon Sep 17 00:00:00 2001 From: avinashhedage Date: Tue, 24 Jan 2023 08:49:35 +0000 Subject: KeyMint200: Fix for ADPU setOutgoing() in T=0 protocol. Added necessary validation before calling APDU setOutgoin() function in T=0 protocol. Test: run vts -m VtsAidlKeyMintTarget Change-Id: I4bd41aa491fc4755a83499953fa98df811a1f2de --- .../javacard/keymaster/KMAndroidSEApplet.java | 16 +++++++ .../javacard/keymaster/KMKeymasterApplet.java | 50 ++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java index b356643..7d84099 100644 --- a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -97,6 +97,20 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis } } + @Override + public void updateApduStatusFlags(short apduIns) { + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 0; + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 1; + switch (apduIns) { + case INS_GET_PROVISION_STATUS_CMD: + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + super.updateApduStatusFlags(apduIns); + } + } + @Override public void process(APDU apdu) { try { @@ -111,6 +125,7 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis if (apduIns == KMType.INVALID_VALUE) { return; } + updateApduStatusFlags(apduIns); if (((KMAndroidSEProvider) seProvider).isPowerReset()) { super.powerReset(); } @@ -409,6 +424,7 @@ public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeLis // required here. byte[] srcBuffer = apdu.getBuffer(); short recvLen = apdu.setIncomingAndReceive(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; short srcOffset = apdu.getOffsetCdata(); short bufferLength = apdu.getIncomingLength(); short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 427634c..7d101e8 100644 --- a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -298,6 +298,10 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // 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. @@ -325,6 +329,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // 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) { @@ -340,6 +347,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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) { @@ -368,6 +376,16 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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); @@ -379,6 +397,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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(); @@ -1325,6 +1344,28 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe } } + 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_RESPONSE_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. * @@ -1768,6 +1809,15 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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); -- cgit v1.2.3 From b9de77f7a0ae5df4a3473c7336ec7b610c59b340 Mon Sep 17 00:00:00 2001 From: Subrahmanyaman Date: Sat, 30 Apr 2022 02:32:40 +0000 Subject: Added android reay_se HAL implementation Test: run vts -m VtsAidlKeyMintTarget Change-Id: I933b892d7ff509ce6005cdce3a9ab18c3938f60a --- ready_se/google/keymint/KM200/HAL/.clang-format | 10 + ready_se/google/keymint/KM200/HAL/Android.bp | 122 +++++ .../google/keymint/KM200/HAL/CborConverter.cpp | 512 +++++++++++++++++++++ ready_se/google/keymint/KM200/HAL/CborConverter.h | 139 ++++++ ready_se/google/keymint/KM200/HAL/ITransport.h | 55 +++ .../keymint/KM200/HAL/JavacardKeyMintDevice.cpp | 454 ++++++++++++++++++ .../keymint/KM200/HAL/JavacardKeyMintDevice.h | 124 +++++ .../keymint/KM200/HAL/JavacardKeyMintOperation.cpp | 300 ++++++++++++ .../keymint/KM200/HAL/JavacardKeyMintOperation.h | 136 ++++++ .../JavacardRemotelyProvisionedComponentDevice.cpp | 284 ++++++++++++ .../JavacardRemotelyProvisionedComponentDevice.h | 80 ++++ .../keymint/KM200/HAL/JavacardSecureElement.cpp | 157 +++++++ .../keymint/KM200/HAL/JavacardSecureElement.h | 114 +++++ .../keymint/KM200/HAL/JavacardSharedSecret.cpp | 61 +++ .../keymint/KM200/HAL/JavacardSharedSecret.h | 34 ++ ready_se/google/keymint/KM200/HAL/LICENSE | 202 ++++++++ ready_se/google/keymint/KM200/HAL/METADATA | 3 + ready_se/google/keymint/KM200/HAL/OWNERS | 3 + .../google/keymint/KM200/HAL/OmapiTransport.cpp | 276 +++++++++++ ready_se/google/keymint/KM200/HAL/OmapiTransport.h | 65 +++ .../google/keymint/KM200/HAL/SocketTransport.cpp | 144 ++++++ .../google/keymint/KM200/HAL/SocketTransport.h | 55 +++ ...ware.hardware_keystore.jc-strongbox-keymint.xml | 17 + ....hardware.security.keymint-service.strongbox.rc | 3 + ...hardware.security.keymint-service.strongbox.xml | 10 + ...are.security.sharedsecret-service.strongbox.xml | 6 + .../google/keymint/KM200/HAL/keymint_utils.cpp | 130 ++++++ ready_se/google/keymint/KM200/HAL/keymint_utils.h | 47 ++ ready_se/google/keymint/KM200/HAL/service.cpp | 95 ++++ 29 files changed, 3638 insertions(+) create mode 100644 ready_se/google/keymint/KM200/HAL/.clang-format create mode 100644 ready_se/google/keymint/KM200/HAL/Android.bp create mode 100644 ready_se/google/keymint/KM200/HAL/CborConverter.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/CborConverter.h create mode 100644 ready_se/google/keymint/KM200/HAL/ITransport.h create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h create mode 100644 ready_se/google/keymint/KM200/HAL/LICENSE create mode 100644 ready_se/google/keymint/KM200/HAL/METADATA create mode 100644 ready_se/google/keymint/KM200/HAL/OWNERS create mode 100644 ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/OmapiTransport.h create mode 100644 ready_se/google/keymint/KM200/HAL/SocketTransport.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/SocketTransport.h create mode 100644 ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml create mode 100644 ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc create mode 100644 ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml create mode 100644 ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml create mode 100644 ready_se/google/keymint/KM200/HAL/keymint_utils.cpp create mode 100644 ready_se/google/keymint/KM200/HAL/keymint_utils.h create mode 100644 ready_se/google/keymint/KM200/HAL/service.cpp diff --git a/ready_se/google/keymint/KM200/HAL/.clang-format b/ready_se/google/keymint/KM200/HAL/.clang-format new file mode 100644 index 0000000..b0dc94c --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +IndentCaseLabels: false +ColumnLimit: 100 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 diff --git a/ready_se/google/keymint/KM200/HAL/Android.bp b/ready_se/google/keymint/KM200/HAL/Android.bp new file mode 100644 index 0000000..a5b1768 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/Android.bp @@ -0,0 +1,122 @@ +// 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. +// + +cc_library { + name: "libjc_keymint", + defaults: [ + "keymaster_defaults", + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "CborConverter.cpp", + "JavacardKeyMintDevice.cpp", + "JavacardKeyMintOperation.cpp", + "JavacardRemotelyProvisionedComponentDevice.cpp", + "JavacardSecureElement.cpp", + "JavacardSharedSecret.cpp", + "keymint_utils.cpp", + ], + cflags: ["-O0"], + shared_libs: [ + "android.hardware.security.secureclock-V1-ndk", + "android.hardware.security.sharedsecret-V1-ndk", + "android.hardware.security.rkp-V3-ndk", + "lib_android_keymaster_keymint_utils", + "libbase", + "libcppbor_external", + "libkeymaster_portable", + "libkeymaster_messages", + "libsoft_attestation_cert", + "liblog", + "libcrypto", + "libcutils", + "libjc_keymint_transport", + "libbinder_ndk", + ], + export_include_dirs: [ + ".", + ], + vendor_available: true, +} + +cc_library { + name: "libjc_keymint_transport", + vendor_available: true, + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "SocketTransport.cpp", + "OmapiTransport.cpp", + ], + export_include_dirs: [ + ".", + ], + shared_libs: [ + "libbinder", + "libbase", + "liblog", + "libbinder_ndk", + "android.se.omapi-V1-ndk", + "libhardware", + ], +} + +cc_binary { + name: "android.hardware.security.keymint-service.strongbox", + relative_install_path: "hw", + init_rc: ["android.hardware.security.keymint-service.strongbox.rc"], + vintf_fragments: [ + "android.hardware.security.keymint-service.strongbox.xml", + "android.hardware.security.sharedsecret-service.strongbox.xml", + ], + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + shared_libs: [ + "android.hardware.security.sharedsecret-V1-ndk", + "lib_android_keymaster_keymint_utils", + "android.hardware.security.rkp-V3-ndk", + "libbase", + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libjc_keymint", + "libjc_keymint_transport", + "liblog", + "libutils", + "android.se.omapi-V1-ndk", + ], + srcs: [ + "service.cpp", + ], + required: [ + "RemoteProvisioner", + "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", + ], +} + +prebuilt_etc { + name: "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", + sub_dir: "permissions", + vendor: true, + src: "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", +} diff --git a/ready_se/google/keymint/KM200/HAL/CborConverter.cpp b/ready_se/google/keymint/KM200/HAL/CborConverter.cpp new file mode 100644 index 0000000..d7e6c11 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/CborConverter.cpp @@ -0,0 +1,512 @@ +/* + ** + ** Copyright 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. + */ + +#include "CborConverter.h" + +#include +#include + +#include + +#include + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::KeyParameterValue; +using ::aidl::android::hardware::security::keymint::SecurityLevel; +using ::aidl::android::hardware::security::keymint::km_utils::kmParam2Aidl; +using ::aidl::android::hardware::security::keymint::km_utils::legacy_enum_conversion; +using ::aidl::android::hardware::security::keymint::km_utils::typeFromTag; + +constexpr int SB_ENFORCED = 0; +constexpr int TEE_ENFORCED = 1; +constexpr int SW_ENFORCED = 2; + +namespace { + +template +std::optional aidlEnumVal2Uint32(const KeyParameterValue& value) { + return (value.getTag() == aidl_tag) + ? std::optional(static_cast(value.get())) + : std::nullopt; +} + +std::optional aidlEnumParam2Uint32(const KeyParameter& param) { + auto tag = legacy_enum_conversion(param.tag); + switch (tag) { + case KM_TAG_PURPOSE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_ALGORITHM: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_BLOCK_MODE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_DIGEST: + case KM_TAG_RSA_OAEP_MGF_DIGEST: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_PADDING: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_EC_CURVE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_USER_AUTH_TYPE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_ORIGIN: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_BLOB_USAGE_REQUIREMENTS: + case KM_TAG_KDF: + default: + CHECK(false) << "Unknown or unused enum tag: Something is broken"; + return std::nullopt; + } +} + +} // namespace + +bool CborConverter::addAttestationKey(Array& array, + const std::optional& attestationKey) { + if (attestationKey.has_value()) { + array.add(Bstr(attestationKey->keyBlob)); + addKeyparameters(array, attestationKey->attestKeyParams); + array.add(Bstr(attestationKey->issuerSubjectName)); + } else { + array.add(std::move(Bstr(vector(0)))); + array.add(std::move(Map())); + array.add(std::move(Bstr(vector(0)))); + } + return true; +} + +bool CborConverter::addKeyparameters(Array& array, const vector& keyParams) { + Map map; + std::map> enum_repetition; + std::map uint_repetition; + for (auto& param : keyParams) { + auto tag = legacy_enum_conversion(param.tag); + switch (typeFromTag(tag)) { + case KM_ENUM: { + auto paramEnum = aidlEnumParam2Uint32(param); + if (paramEnum.has_value()) { + map.add(static_cast(tag), *paramEnum); + } + break; + } + case KM_UINT: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get(); + map.add(static_cast(tag), intVal); + } + break; + case KM_UINT_REP: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get(); + uint_repetition[static_cast(tag)].add(intVal); + } + break; + case KM_ENUM_REP: { + auto paramEnumRep = aidlEnumParam2Uint32(param); + if (paramEnumRep.has_value()) { + enum_repetition[static_cast(tag)].push_back(*paramEnumRep); + } + break; + } + case KM_ULONG: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get(); + map.add(static_cast(tag), longVal); + } + break; + case KM_ULONG_REP: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get(); + uint_repetition[static_cast(tag & 0x00000000ffffffff)].add(longVal); + } + break; + case KM_DATE: + if (param.value.getTag() == KeyParameterValue::dateTime) { + auto dateVal = param.value.get(); + map.add(static_cast(tag), dateVal); + } + break; + case KM_BOOL: + map.add(static_cast(tag), 1 /* true */); + break; + case KM_BIGNUM: + case KM_BYTES: + if (param.value.getTag() == KeyParameterValue::blob) { + const auto& value = param.value.get(); + map.add(static_cast(tag & 0x00000000ffffffff), value); + } + break; + case KM_INVALID: + break; + } + } + + for (auto const& [key, val] : enum_repetition) { + Bstr bstr(val); + map.add(key, std::move(bstr)); + } + + for (auto& [key, val] : uint_repetition) { + map.add(key, std::move(val)); + } + array.add(std::move(map)); + return true; +} + +// Array of three maps +std::optional> +CborConverter::getKeyCharacteristics(const unique_ptr& item, const uint32_t pos) { + vector keyCharacteristics; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + KeyCharacteristics swEnf{SecurityLevel::KEYSTORE, {}}; + KeyCharacteristics teeEnf{SecurityLevel::TRUSTED_ENVIRONMENT, {}}; + KeyCharacteristics sbEnf{SecurityLevel::STRONGBOX, {}}; + + auto optSbEnf = getKeyParameters(arrayItem.value(), SB_ENFORCED); + if (!optSbEnf) { + return std::nullopt; + } + sbEnf.authorizations = std::move(optSbEnf.value()); + auto optTeeEnf = getKeyParameters(arrayItem.value(), TEE_ENFORCED); + if (!optTeeEnf) { + return std::nullopt; + } + teeEnf.authorizations = std::move(optTeeEnf.value()); + auto optSwEnf = getKeyParameters(arrayItem.value(), SW_ENFORCED); + if (!optSwEnf) { + return std::nullopt; + } + swEnf.authorizations = std::move(optSwEnf.value()); + // VTS will fail if the authorizations list is empty. + if (!sbEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(sbEnf)); + if (!teeEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(teeEnf)); + if (!swEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(swEnf)); + return keyCharacteristics; +} + +std::optional> CborConverter::getKeyParameter( + const std::pair&, const std::unique_ptr&> pair) { + std::vector keyParams; + keymaster_tag_t key; + auto optValue = getUint64(pair.first); + if (!optValue) { + return std::nullopt; + } + key = static_cast(optValue.value()); + switch (keymaster_tag_get_type(key)) { + case KM_ENUM_REP: { + /* ENUM_REP contains values encoded in a Byte string */ + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) { + return std::nullopt; + } + for (auto bchar : bstr->value()) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + keyParam.enumerated = bchar; + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ENUM: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.enumerated = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.integer = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_ULONG: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT_REP: { + /* UINT_REP contains values encoded in a Array */ + Array* array = const_cast(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.integer = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ULONG_REP: { + /* ULONG_REP contains values encoded in a Array */ + Array* array = const_cast(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_DATE: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.date_time = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BOOL: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + // If a tag with this type is present, the value is true. If absent, false. + keyParam.boolean = true; + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BIGNUM: + case KM_BYTES: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) return std::nullopt; + keyParam.blob.data = bstr->value().data(); + keyParam.blob.data_length = bstr->value().size(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_INVALID: + break; + } + return std::nullopt; +} + +// array of a blobs +std::optional> +CborConverter::getCertificateChain(const std::unique_ptr& item, const uint32_t pos) { + vector certChain; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) return std::nullopt; + + const Array* arr = arrayItem.value().get()->asArray(); + for (int i = 0; i < arr->size(); i++) { + Certificate cert; + auto optTemp = getByteArrayVec(arrayItem.value(), i); + if (!optTemp) return std::nullopt; + cert.encodedCertificate = std::move(optTemp.value()); + certChain.push_back(std::move(cert)); + } + return certChain; +} + +std::optional CborConverter::getByteArrayStr(const unique_ptr& item, + const uint32_t pos) { + auto optTemp = getByteArrayVec(item, pos); + if (!optTemp) { + return std::nullopt; + } + std::string str(optTemp->begin(), optTemp->end()); + return str; +} + +std::optional> CborConverter::getByteArrayVec(const unique_ptr& item, + const uint32_t pos) { + auto strItem = getItemAtPos(item, pos); + if (!strItem || (MajorType::BSTR != getType(strItem.value()))) { + return std::nullopt; + } + const Bstr* bstr = strItem.value().get()->asBstr(); + return bstr->value(); +} + +std::optional +CborConverter::getSharedSecretParameters(const unique_ptr& item, const uint32_t pos) { + SharedSecretParameters params; + // Array [seed, nonce] + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + auto optSeed = getByteArrayVec(arrayItem.value(), 0); + auto optNonce = getByteArrayVec(arrayItem.value(), 1); + if (!optSeed || !optNonce) { + return std::nullopt; + } + params.seed = std::move(optSeed.value()); + params.nonce = std::move(optNonce.value()); + return params; +} + +bool CborConverter::addSharedSecretParameters(Array& array, + const vector& params) { + Array cborParamsVec; + for (auto param : params) { + Array cborParam; + cborParam.add(Bstr(param.seed)); + cborParam.add(Bstr(param.nonce)); + cborParamsVec.add(std::move(cborParam)); + } + array.add(std::move(cborParamsVec)); + return true; +} + +bool CborConverter::addTimeStampToken(Array& array, const TimeStampToken& token) { + Array vToken; + vToken.add(static_cast(token.challenge)); + vToken.add(static_cast(token.timestamp.milliSeconds)); + vToken.add((std::vector(token.mac))); + array.add(std::move(vToken)); + return true; +} + +bool CborConverter::addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken) { + + Array hwAuthToken; + hwAuthToken.add(static_cast(authToken.challenge)); + hwAuthToken.add(static_cast(authToken.userId)); + hwAuthToken.add(static_cast(authToken.authenticatorId)); + hwAuthToken.add(static_cast(authToken.authenticatorType)); + hwAuthToken.add(static_cast(authToken.timestamp.milliSeconds)); + hwAuthToken.add((std::vector(authToken.mac))); + array.add(std::move(hwAuthToken)); + return true; +} + +std::optional CborConverter::getTimeStampToken(const unique_ptr& item, + const uint32_t pos) { + TimeStampToken token; + // {challenge, timestamp, Mac} + auto optChallenge = getUint64(item, pos); + auto optTimestampMillis = getUint64(item, pos + 1); + auto optTemp = getByteArrayVec(item, pos + 2); + if (!optChallenge || !optTimestampMillis || !optTemp) { + return std::nullopt; + } + token.mac = std::move(optTemp.value()); + token.challenge = static_cast(std::move(optChallenge.value())); + token.timestamp.milliSeconds = static_cast(std::move(optTimestampMillis.value())); + return token; +} + +std::optional CborConverter::getArrayItem(const std::unique_ptr& item, + const uint32_t pos) { + Array array; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + array = std::move(*(arrayItem.value().get()->asArray())); + return array; +} + +std::optional CborConverter::getMapItem(const std::unique_ptr& item, + const uint32_t pos) { + Map map; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) { + return std::nullopt; + } + map = std::move(*(mapItem.value().get()->asMap())); + return map; +} + +std::optional> CborConverter::getKeyParameters(const unique_ptr& item, + const uint32_t pos) { + vector params; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) return std::nullopt; + const Map* map = mapItem.value().get()->asMap(); + size_t mapSize = map->size(); + for (int i = 0; i < mapSize; i++) { + auto optKeyParams = getKeyParameter((*map)[i]); + if (optKeyParams) { + params.insert(params.end(), optKeyParams->begin(), optKeyParams->end()); + } else { + return std::nullopt; + } + } + return params; +} + +std::tuple, keymaster_error_t> +CborConverter::decodeData(const std::vector& response) { + auto [item, pos, message] = cppbor::parse(response); + if (!item || MajorType::ARRAY != getType(item)) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + auto optErrorCode = getErrorCode(item, 0); + if (!optErrorCode) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + return {std::move(item), optErrorCode.value()}; +} + +std::optional +CborConverter::getErrorCode(const std::unique_ptr& item, const uint32_t pos) { + auto optErrorVal = getUint64(item, pos); + if (!optErrorVal) { + return std::nullopt; + } + return static_cast(0 - optErrorVal.value()); +} + +std::optional CborConverter::getUint64(const unique_ptr& item) { + if ((item == nullptr) || (MajorType::UINT != getType(item))) { + return std::nullopt; + } + const Uint* uintVal = item.get()->asUint(); + return uintVal->unsignedValue(); +} + +std::optional CborConverter::getUint64(const unique_ptr& item, const uint32_t pos) { + auto intItem = getItemAtPos(item, pos); + if (!intItem) { + return std::nullopt; + } + return getUint64(intItem.value()); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/CborConverter.h b/ready_se/google/keymint/KM200/HAL/CborConverter.h new file mode 100644 index 0000000..b49273b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/CborConverter.h @@ -0,0 +1,139 @@ +/* + ** + ** Copyright 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. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +namespace keymint::javacard { +using aidl::android::hardware::security::keymint::AttestationKey; +using aidl::android::hardware::security::keymint::Certificate; +using aidl::android::hardware::security::keymint::HardwareAuthToken; +using aidl::android::hardware::security::keymint::KeyCharacteristics; +using aidl::android::hardware::security::keymint::KeyParameter; +using aidl::android::hardware::security::secureclock::TimeStampToken; +using aidl::android::hardware::security::sharedsecret::SharedSecretParameters; +using cppbor::Array; +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Item; +using cppbor::MajorType; +using cppbor::Map; +using cppbor::Nint; +using cppbor::Uint; +using std::string; +using std::unique_ptr; +using std::vector; + +class CborConverter { + public: + CborConverter() = default; + + ~CborConverter() = default; + + std::tuple, keymaster_error_t> + decodeData(const std::vector& response); + + std::optional getUint64(const unique_ptr& item); + + std::optional getUint64(const unique_ptr& item, const uint32_t pos); + + std::optional + getSharedSecretParameters(const std::unique_ptr& item, const uint32_t pos); + + std::optional getByteArrayStr(const unique_ptr& item, const uint32_t pos); + + std::optional> getByteArrayVec(const unique_ptr& item, + const uint32_t pos); + + std::optional> getKeyParameters(const unique_ptr& item, + const uint32_t pos); + + bool addKeyparameters(Array& array, const vector& keyParams); + + bool addAttestationKey(Array& array, const std::optional& attestationKey); + + bool addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken); + + bool addSharedSecretParameters(Array& array, const vector& params); + + std::optional getTimeStampToken(const std::unique_ptr& item, + const uint32_t pos); + + std::optional> + getKeyCharacteristics(const std::unique_ptr& item, const uint32_t pos); + + std::optional> getCertificateChain(const std::unique_ptr& item, + const uint32_t pos); + + std::optional>> getMultiByteArray(const unique_ptr& item, + const uint32_t pos); + + bool addTimeStampToken(Array& array, const TimeStampToken& token); + + std::optional getMapItem(const std::unique_ptr& item, const uint32_t pos); + + std::optional getArrayItem(const std::unique_ptr& item, const uint32_t pos); + + std::optional getErrorCode(const std::unique_ptr& item, + const uint32_t pos); + + private: + /** + * Get the type of the Item pointer. + */ + inline MajorType getType(const unique_ptr& item) { return item.get()->type(); } + + /** + * Construct Keyparameter structure from the pair of key and value. If TagType is ENUM_REP the + * value contains binary string. If TagType is UINT_REP or ULONG_REP the value contains Array of + * unsigned integers. + */ + std::optional> getKeyParameter( + const std::pair&, const std::unique_ptr&> pair); + + /** + * Get the sub item pointer from the root item pointer at the given position. + */ + inline std::optional> getItemAtPos(const unique_ptr& item, + const uint32_t pos) { + Array* arr = nullptr; + + if (MajorType::ARRAY != getType(item)) { + return std::nullopt; + } + arr = const_cast(item.get()->asArray()); + if (arr->size() < (pos + 1)) { + return std::nullopt; + } + return std::move((*arr)[pos]); + } +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/ITransport.h b/ready_se/google/keymint/KM200/HAL/ITransport.h new file mode 100644 index 0000000..ca100be --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/ITransport.h @@ -0,0 +1,55 @@ +/* + ** + ** Copyright 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. + */ +#pragma once + +#include +#include + +#include + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +/** + * ITransport is an interface with a set of virtual methods that allow communication between the + * HAL and the applet on the secure element. + */ +class ITransport { + public: + virtual ~ITransport() {} + + /** + * Opens connection. + */ + virtual keymaster_error_t openConnection() = 0; + /** + * Send data over communication channel and receives data back from the remote end. + */ + virtual keymaster_error_t sendData(const vector& inData, vector& output) = 0; + /** + * Closes the connection. + */ + virtual keymaster_error_t closeConnection() = 0; + /** + * Returns the state of the connection status. Returns true if the connection is active, false + * if connection is broken. + */ + virtual bool isConnected() = 0; +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp new file mode 100644 index 0000000..bd68b48 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp @@ -0,0 +1,454 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" + +#include "JavacardKeyMintDevice.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "JavacardKeyMintOperation.h" +#include "JavacardSharedSecret.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Uint; +using ::keymaster::AuthorizationSet; +using ::keymaster::dup_buffer; +using ::keymaster::KeymasterBlob; +using ::keymaster::KeymasterKeyBlob; +using ::keymint::javacard::Instruction; +using std::string; + +ScopedAStatus JavacardKeyMintDevice::defaultHwInfo(KeyMintHardwareInfo* info) { + info->versionNumber = 2; + info->keyMintAuthorName = "Google"; + info->keyMintName = "JavacardKeymintDevice"; + info->securityLevel = securitylevel_; + info->timestampTokenRequired = true; + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_HW_INFO_CMD); + std::optional optKeyMintName; + std::optional optKeyMintAuthorName; + std::optional optSecLevel; + std::optional optVersion; + std::optional optTsRequired; + if (err != KM_ERROR_OK || !(optVersion = cbor_.getUint64(item, 1)) || + !(optSecLevel = cbor_.getUint64(item, 2)) || + !(optKeyMintName = cbor_.getByteArrayStr(item, 3)) || + !(optKeyMintAuthorName = cbor_.getByteArrayStr(item, 4)) || + !(optTsRequired = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + card_->initializeJavacard(); + info->keyMintName = std::move(optKeyMintName.value()); + info->keyMintAuthorName = std::move(optKeyMintAuthorName.value()); + info->timestampTokenRequired = (optTsRequired.value() == 1); + info->securityLevel = static_cast(std::move(optSecLevel.value())); + info->versionNumber = static_cast(std::move(optVersion.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::generateKey(const vector& keyParams, + const optional& attestationKey, + KeyCreationResult* creationResult) { + cppbor::Array array; + // add key params + cbor_.addKeyparameters(array, keyParams); + // add attestation key if any + cbor_.addAttestationKey(array, attestationKey); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding og response in generateKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::addRngEntropy(const vector& data) { + cppbor::Array request; + // add key data + request.add(Bstr(data)); + auto [item, err] = card_->sendRequest(Instruction::INS_ADD_RNG_ENTROPY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending addRngEntropy."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::importKey(const vector& keyParams, + KeyFormat keyFormat, const vector& keyData, + const optional& attestationKey, + KeyCreationResult* creationResult) { + + cppbor::Array request; + // add key params + cbor_.addKeyparameters(request, keyParams); + // add key format + request.add(Uint(static_cast(keyFormat))); + // add key data + request.add(Bstr(keyData)); + // add attestation key if any + cbor_.addAttestationKey(request, attestationKey); + + auto [item, err] = card_->sendRequest(Instruction::INS_IMPORT_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in importKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding response in importKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +// import wrapped key is divided into 2 stage operation. +ScopedAStatus JavacardKeyMintDevice::importWrappedKey(const vector& wrappedKeyData, + const vector& wrappingKeyBlob, + const vector& maskingKey, + const vector& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) { + cppbor::Array request; + std::unique_ptr item; + vector keyBlob; + std::vector response; + vector keyCharacteristics; + std::vector iv; + std::vector transitKey; + std::vector secureKey; + std::vector tag; + vector authList; + KeyFormat keyFormat; + std::vector wrappedKeyDescription; + keymaster_error_t errorCode = parseWrappedKey(wrappedKeyData, iv, transitKey, secureKey, tag, + authList, keyFormat, wrappedKeyDescription); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in parse wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + + // begin import + std::tie(item, errorCode) = + sendBeginImportWrappedKeyCmd(transitKey, wrappingKeyBlob, maskingKey, unwrappingParams); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send begin import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + // Finish the import + std::tie(item, errorCode) = sendFinishImportWrappedKeyCmd( + authList, keyFormat, secureKey, tag, iv, wrappedKeyDescription, passwordSid, biometricSid); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send finish import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding the response in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +std::tuple, keymaster_error_t> +JavacardKeyMintDevice::sendBeginImportWrappedKeyCmd(const std::vector& transitKey, + const std::vector& wrappingKeyBlob, + const std::vector& maskingKey, + const vector& unwrappingParams) { + Array request; + request.add(std::vector(transitKey)); + request.add(std::vector(wrappingKeyBlob)); + request.add(std::vector(maskingKey)); + cbor_.addKeyparameters(request, unwrappingParams); + return card_->sendRequest(Instruction::INS_BEGIN_IMPORT_WRAPPED_KEY_CMD, request); +} + +std::tuple, keymaster_error_t> +JavacardKeyMintDevice::sendFinishImportWrappedKeyCmd( + const vector& keyParams, KeyFormat keyFormat, + const std::vector& secureKey, const std::vector& tag, + const std::vector& iv, const std::vector& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid) { + Array request; + cbor_.addKeyparameters(request, keyParams); + request.add(static_cast(keyFormat)); + request.add(std::vector(secureKey)); + request.add(std::vector(tag)); + request.add(std::vector(iv)); + request.add(std::vector(wrappedKeyDescription)); + request.add(Uint(passwordSid)); + request.add(Uint(biometricSid)); + return card_->sendRequest(Instruction::INS_FINISH_IMPORT_WRAPPED_KEY_CMD, request); +} + +ScopedAStatus JavacardKeyMintDevice::upgradeKey(const vector& keyBlobToUpgrade, + const vector& upgradeParams, + vector* keyBlob) { + cppbor::Array request; + // add key blob + request.add(Bstr(keyBlobToUpgrade)); + // add key params + cbor_.addKeyparameters(request, upgradeParams); + auto [item, err] = card_->sendRequest(Instruction::INS_UPGRADE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + if (!optKeyBlob) { + LOG(ERROR) << "Error in decoding the response in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteKey(const vector& keyBlob) { + Array request; + request.add(Bstr(keyBlob)); + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteKey."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteAllKeys() { + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_ALL_KEYS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteAllKeys."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::destroyAttestationIds() { + auto [item, err] = card_->sendRequest(Instruction::INS_DESTROY_ATT_IDS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in destroyAttestationIds."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::begin(KeyPurpose purpose, const std::vector& keyBlob, + const std::vector& params, + const std::optional& authToken, + BeginResult* result) { + + cppbor::Array array; + std::vector response; + // make request + array.add(Uint(static_cast(purpose))); + array.add(Bstr(keyBlob)); + cbor_.addKeyparameters(array, params); + HardwareAuthToken token = authToken.value_or(HardwareAuthToken()); + cbor_.addHardwareAuthToken(array, token); + + // Send earlyBootEnded if there is any pending earlybootEnded event. + auto retErr = card_->sendEarlyBootEndedEvent(false); + if (retErr != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(retErr); + ; + } + + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_OPERATION_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in begin."; + return km_utils::kmError2ScopedAStatus(err); + } + // return the result + auto keyParams = cbor_.getKeyParameters(item, 1); + auto optOpHandle = cbor_.getUint64(item, 2); + auto optBufMode = cbor_.getUint64(item, 3); + auto optMacLength = cbor_.getUint64(item, 4); + + if (!keyParams || !optOpHandle || !optBufMode || !optMacLength) { + LOG(ERROR) << "Error in decoding the response in begin."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + result->params = std::move(keyParams.value()); + result->challenge = optOpHandle.value(); + result->operation = ndk::SharedRefBase::make( + static_cast(optOpHandle.value()), + static_cast(optBufMode.value()), optMacLength.value(), card_); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardKeyMintDevice::deviceLocked(bool passwordOnly, + const std::optional& timestampToken) { + Array request; + int8_t password = 1; + if (!passwordOnly) { + password = 0; + } + request.add(Uint(password)); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_DEVICE_LOCKED_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::earlyBootEnded() { + auto err = card_->sendEarlyBootEndedEvent(true); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBootEndedEvent."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getKeyCharacteristics( + const std::vector& keyBlob, const std::vector& appId, + const std::vector& appData, std::vector* result) { + cppbor::Array request; + request.add(vector(keyBlob)); + request.add(vector(appId)); + request.add(vector(appData)); + auto [item, err] = card_->sendRequest(Instruction::INS_GET_KEY_CHARACTERISTICS_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getKeyCharacteristics."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyChars = cbor_.getKeyCharacteristics(item, 1); + if (!optKeyChars) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *result = std::move(optKeyChars.value()); + return ScopedAStatus::ok(); +} + +keymaster_error_t +JavacardKeyMintDevice::parseWrappedKey(const vector& wrappedKeyData, + std::vector& iv, std::vector& transitKey, + std::vector& secureKey, std::vector& tag, + vector& authList, KeyFormat& keyFormat, + std::vector& wrappedKeyDescription) { + KeymasterBlob kmIv; + KeymasterKeyBlob kmTransitKey; + KeymasterKeyBlob kmSecureKey; + KeymasterBlob kmTag; + AuthorizationSet authSet; + keymaster_key_format_t kmKeyFormat; + KeymasterBlob kmWrappedKeyDescription; + + size_t keyDataLen = wrappedKeyData.size(); + uint8_t* keyData = dup_buffer(wrappedKeyData.data(), keyDataLen); + keymaster_key_blob_t keyMaterial = {keyData, keyDataLen}; + keymaster_error_t error = + parse_wrapped_key(KeymasterKeyBlob(keyMaterial), &kmIv, &kmTransitKey, &kmSecureKey, &kmTag, + &authSet, &kmKeyFormat, &kmWrappedKeyDescription); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error parsing wrapped key."; + return error; + } + iv = km_utils::kmBlob2vector(kmIv); + transitKey = km_utils::kmBlob2vector(kmTransitKey); + secureKey = km_utils::kmBlob2vector(kmSecureKey); + tag = km_utils::kmBlob2vector(kmTag); + authList = km_utils::kmParamSet2Aidl(authSet); + keyFormat = static_cast(kmKeyFormat); + wrappedKeyDescription = km_utils::kmBlob2vector(kmWrappedKeyDescription); + return KM_ERROR_OK; +} + +ScopedAStatus JavacardKeyMintDevice::convertStorageKeyToEphemeral( + const std::vector& /* storageKeyBlob */, + std::vector* /* ephemeralKeyBlob */) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrustChallenge(array* challenge) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_ROT_CHALLENGE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getRootOfTrustChallenge."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optChallenge = cbor_.getByteArrayVec(item, 1); + if (!optChallenge) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + std::move(optChallenge->begin(), optChallenge->begin() + 16, challenge->begin()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrust(const array& /*challenge*/, + vector* /*rootOfTrust*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::sendRootOfTrust(const vector& rootOfTrust) { + cppbor::Array request; + request.add(EncodedItem(rootOfTrust)); // taggedItem. + auto [item, err] = card_->sendRequest(Instruction::INS_SEND_ROT_DATA_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in sendRootOfTrust."; + return km_utils::kmError2ScopedAStatus(err); + } + LOG(INFO) << "JavacardKeyMintDevice::sendRootOfTrust success"; + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h new file mode 100644 index 0000000..adf0f7d --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h @@ -0,0 +1,124 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::array; +using std::optional; +using std::shared_ptr; +using std::vector; + +class JavacardKeyMintDevice : public BnKeyMintDevice { + public: + explicit JavacardKeyMintDevice(shared_ptr card) + : securitylevel_(SecurityLevel::STRONGBOX), card_(card) { + card_->initializeJavacard(); + } + virtual ~JavacardKeyMintDevice() {} + + ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* info) override; + + ScopedAStatus addRngEntropy(const vector& data) override; + + ScopedAStatus generateKey(const vector& keyParams, + const optional& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importKey(const vector& keyParams, KeyFormat keyFormat, + const vector& keyData, + const optional& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importWrappedKey(const vector& wrappedKeyData, + const vector& wrappingKeyBlob, + const vector& maskingKey, + const vector& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) override; + + ScopedAStatus upgradeKey(const vector& keyBlobToUpgrade, + const vector& upgradeParams, + vector* keyBlob) override; + + ScopedAStatus deleteKey(const vector& keyBlob) override; + ScopedAStatus deleteAllKeys() override; + ScopedAStatus destroyAttestationIds() override; + + virtual ScopedAStatus begin(KeyPurpose in_purpose, const std::vector& in_keyBlob, + const std::vector& in_params, + const std::optional& in_authToken, + BeginResult* _aidl_return) override; + + ScopedAStatus deviceLocked(bool passwordOnly, + const optional& timestampToken) override; + + ScopedAStatus earlyBootEnded() override; + + ScopedAStatus getKeyCharacteristics(const std::vector& in_keyBlob, + const std::vector& in_appId, + const std::vector& in_appData, + std::vector* _aidl_return) override; + + ScopedAStatus convertStorageKeyToEphemeral(const std::vector& storageKeyBlob, + std::vector* ephemeralKeyBlob) override; + + ScopedAStatus getRootOfTrustChallenge(array* challenge) override; + + ScopedAStatus getRootOfTrust(const array& challenge, + vector* rootOfTrust) override; + + ScopedAStatus sendRootOfTrust(const vector& rootOfTrust) override; + + private: + keymaster_error_t parseWrappedKey(const vector& wrappedKeyData, + std::vector& iv, std::vector& transitKey, + std::vector& secureKey, std::vector& tag, + vector& authList, KeyFormat& keyFormat, + std::vector& wrappedKeyDescription); + + std::tuple, keymaster_error_t> sendBeginImportWrappedKeyCmd( + const std::vector& transitKey, const std::vector& wrappingKeyBlob, + const std::vector& maskingKey, const vector& unwrappingParams); + + std::tuple, keymaster_error_t> + sendFinishImportWrappedKeyCmd(const vector& keyParams, KeyFormat keyFormat, + const std::vector& secureKey, + const std::vector& tag, const std::vector& iv, + const std::vector& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid); + + ScopedAStatus defaultHwInfo(KeyMintHardwareInfo* info); + + const SecurityLevel securitylevel_; + const shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp new file mode 100644 index 0000000..a46f066 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" + +#include "JavacardKeyMintOperation.h" + +#include +#include +#include +#include + +#include "CborConverter.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::Uint; +using secureclock::TimeStampToken; + +JavacardKeyMintOperation::~JavacardKeyMintOperation() { + if (opHandle_ != 0) { + JavacardKeyMintOperation::abort(); + } +} + +ScopedAStatus JavacardKeyMintOperation::updateAad(const vector& input, + const optional& authToken, + const optional& timestampToken) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken.value_or(HardwareAuthToken())); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_AAD_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintOperation::update(const vector& input, + const optional& authToken, + const optional& timestampToken, + vector* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + DataView view = {.buffer = {}, .data = input, .start = 0, .length = input.size()}; + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + if (view.length > MAX_CHUNK_SIZE) { + err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + vector remaining = popNextChunk(view, view.length); + err = sendUpdate(remaining, aToken, tToken, *output); + } + return km_utils::kmError2ScopedAStatus(err); +} + +ScopedAStatus JavacardKeyMintOperation::finish(const optional>& input, + const optional>& signature, + const optional& authToken, + const optional& timestampToken, + const optional>& confirmationToken, + vector* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + const vector confToken = confirmationToken.value_or(vector()); + const vector inData = input.value_or(vector()); + DataView view = {.buffer = {}, .data = inData, .start = 0, .length = inData.size()}; + const vector sign = signature.value_or(vector()); + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + appendBufferedData(view); + if (view.length > MAX_CHUNK_SIZE) { + auto err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + } else { + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + appendBufferedData(view); + } + vector remaining = popNextChunk(view, view.length); + return km_utils::kmError2ScopedAStatus( + sendFinish(remaining, sign, aToken, tToken, confToken, *output)); +} + +ScopedAStatus JavacardKeyMintOperation::abort() { + Array request; + request.add(Uint(opHandle_)); + auto [item, err] = card_->sendRequest(Instruction::INS_ABORT_OPERATION_CMD, request); + opHandle_ = 0; + buffer_.clear(); + return km_utils::kmError2ScopedAStatus(err); +} + +void JavacardKeyMintOperation::blockAlign(DataView& view, uint16_t blockSize) { + appendBufferedData(view); + uint16_t offset = getDataViewOffset(view, blockSize); + if (view.buffer.empty() && !view.data.empty()) { + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } else if (view.data.empty() && !view.buffer.empty()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + } else { + if (offset < view.buffer.size()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + } else { + offset = offset - view.buffer.size(); + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } + } + // adjust the view length by removing the buffered data size from it. + view.length = view.length - buffer_.size(); +} + +uint16_t JavacardKeyMintOperation::getDataViewOffset(DataView& view, uint16_t blockSize) { + uint16_t offset = 0; + uint16_t remaining = 0; + switch (bufferingMode_) { + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + remaining = (view.length % blockSize); + if (offset >= blockSize && remaining == 0) { + offset -= blockSize; + } + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + if (view.length > macLength_) { + offset = (view.length - macLength_); + } + break; + default: + break; + } + return offset; +} + +keymaster_error_t JavacardKeyMintOperation::bufferData(DataView& view) { + if (view.data.empty()) return KM_ERROR_OK; // nothing to buffer + switch (bufferingMode_) { + case BufferingMode::RSA_DECRYPT_OR_NO_DIGEST: + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + if (buffer_.size() > RSA_BUFFER_SIZE) { + abort(); + return KM_ERROR_INVALID_INPUT_LENGTH; + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::EC_NO_DIGEST: + if (buffer_.size() < EC_BUFFER_SIZE) { + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + // Truncate the buffered data if greater then allowed EC buffer size. + if (buffer_.size() > EC_BUFFER_SIZE) { + buffer_.erase(buffer_.begin() + EC_BUFFER_SIZE, buffer_.end()); + } + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, AES_BLOCK_SIZE); + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + blockAlign(view, macLength_); + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, DES_BLOCK_SIZE); + break; + case BufferingMode::NONE: + break; + } + return KM_ERROR_OK; +} + +// Incrementally send the request using multiple updates. +keymaster_error_t JavacardKeyMintOperation::updateInChunks(DataView& view, + HardwareAuthToken& authToken, + TimeStampToken& timestampToken, + vector* output) { + keymaster_error_t sendError = KM_ERROR_UNKNOWN_ERROR; + while (view.length > MAX_CHUNK_SIZE) { + vector chunk = popNextChunk(view, MAX_CHUNK_SIZE); + sendError = sendUpdate(chunk, authToken, timestampToken, *output); + if (sendError != KM_ERROR_OK) { + return sendError; + } + // Clear tokens + if (!authToken.mac.empty()) authToken = HardwareAuthToken(); + if (!timestampToken.mac.empty()) timestampToken = TimeStampToken(); + } + return KM_ERROR_OK; +} + +vector JavacardKeyMintOperation::popNextChunk(DataView& view, uint32_t chunkSize) { + uint32_t start = view.start; + uint32_t end = start + ((view.length < chunkSize) ? view.length : chunkSize); + vector chunk; + if (start < view.buffer.size()) { + if (end < view.buffer.size()) { + chunk = {view.buffer.begin() + start, view.buffer.begin() + end}; + } else { + end = end - view.buffer.size(); + chunk = {view.buffer.begin() + start, view.buffer.end()}; + chunk.insert(chunk.end(), view.data.begin(), view.data.begin() + end); + } + } else { + start = start - view.buffer.size(); + end = end - view.buffer.size(); + chunk = {view.data.begin() + start, view.data.begin() + end}; + } + view.start = view.start + chunk.size(); + view.length = view.length - chunk.size(); + return chunk; +} + +keymaster_error_t JavacardKeyMintOperation::sendUpdate(const vector& input, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + vector& output) { + if (input.empty()) { + return KM_ERROR_OK; + } + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + auto [item, error] = card_->sendRequest(Instruction::INS_UPDATE_OPERATION_CMD, request); + if (error != KM_ERROR_OK) { + return error; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector& data, + const vector& sign, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector& confToken, + vector& output) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(data)); + request.add(Bstr(sign)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + request.add(Bstr(confToken)); + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return err; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + opHandle_ = 0; + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h new file mode 100644 index 0000000..c1d967a --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h @@ -0,0 +1,136 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +#define AES_BLOCK_SIZE 16 +#define DES_BLOCK_SIZE 8 +#define RSA_BUFFER_SIZE 256 +#define EC_BUFFER_SIZE 32 +#define MAX_CHUNK_SIZE 256 + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::Instruction; +using ::keymint::javacard::JavacardSecureElement; +using ::ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::optional; +using std::shared_ptr; +using std::vector; + +// Bufferig modes for update +enum class BufferingMode : int32_t { + NONE = 0, // Send everything to javacard - most of the assymteric operations + RSA_DECRYPT_OR_NO_DIGEST = + 1, // Buffer everything in update upto 256 bytes and send in finish. If + // input data is greater then 256 bytes then it is an error. Javacard + // will further check according to exact key size and crypto provider. + EC_NO_DIGEST = 2, // Buffer upto 65 bytes and then truncate. Javacard will further truncate + // upto exact keysize. + BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 3, // Buffer 16 bytes. + BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED = 4, // Buffer 16 bytes. + BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 5, // Buffer 8 bytes. + BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED = 6, // Buffer 8 bytes. + BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED = 7, // Buffer 16 bytes. + +}; + +// The is the view in the input data being processed by update/finish funcion. + +struct DataView { + vector buffer; // previously buffered data from cycle n-1 + const vector& data; // current data in cycle n. + uint32_t start; // start of the view + size_t length; // length of the view +}; + +class JavacardKeyMintOperation : public BnKeyMintOperation { + public: + explicit JavacardKeyMintOperation(keymaster_operation_handle_t opHandle, + BufferingMode bufferingMode, uint16_t macLength, + shared_ptr card) + : buffer_(vector()), bufferingMode_(bufferingMode), macLength_(macLength), + card_(card), opHandle_(opHandle) {} + virtual ~JavacardKeyMintOperation(); + + ScopedAStatus updateAad(const vector& input, + const optional& authToken, + const optional& timestampToken) override; + + ScopedAStatus update(const vector& input, const optional& authToken, + const optional& timestampToken, + vector* output) override; + + ScopedAStatus finish(const optional>& input, + const optional>& signature, + const optional& authToken, + const optional& timestampToken, + const optional>& confirmationToken, + vector* output) override; + + ScopedAStatus abort() override; + + private: + vector popNextChunk(DataView& view, uint32_t chunkSize); + + keymaster_error_t updateInChunks(DataView& data, HardwareAuthToken& authToken, + TimeStampToken& timestampToken, vector* output); + + keymaster_error_t sendFinish(const vector& data, const vector& signature, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector& confToken, vector& output); + + keymaster_error_t sendUpdate(const vector& data, const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, vector& output); + + inline void appendBufferedData(DataView& view) { + if (!buffer_.empty()) { + view.buffer = buffer_; + view.length = view.length + buffer_.size(); + view.start = 0; + // view.buffer = insert(data.begin(), buffer_.begin(), buffer_.end()); + buffer_.clear(); + } + } + + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + keymaster_error_t bufferData(DataView& data); + void blockAlign(DataView& data, uint16_t blockSize); + uint16_t getDataViewOffset(DataView& view, uint16_t blockSize); + + vector buffer_; + BufferingMode bufferingMode_; + uint16_t macLength_; + const shared_ptr card_; + keymaster_operation_handle_t opHandle_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp new file mode 100644 index 0000000..fe9821b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.rkp.strongbox-impl" + +#include "JavacardRemotelyProvisionedComponentDevice.h" + +#include + +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::EncodedItem; +using cppcose::kCoseMac0EntryCount; +using cppcose::kCoseMac0Payload; +using ::keymint::javacard::Instruction; +using std::string; + +// RKP error codes defined in keymint applet. +constexpr int32_t kStatusFailed = 32000; +constexpr int32_t kStatusInvalidMac = 32001; +constexpr int32_t kStatusProductionKeyInTestRequest = 32002; +constexpr int32_t kStatusTestKeyInProductionRequest = 32003; +constexpr int32_t kStatusInvalidEek = 32004; +constexpr int32_t kStatusInvalidState = 32005; + +namespace { + +keymaster_error_t translateRkpErrorCode(int32_t error) { + switch (-error) { + case kStatusFailed: + case kStatusInvalidState: + return static_cast(BnRemotelyProvisionedComponent::STATUS_FAILED); + case kStatusInvalidMac: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); + case kStatusProductionKeyInTestRequest: + return static_cast( + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + case kStatusTestKeyInProductionRequest: + return static_cast( + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + case kStatusInvalidEek: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } + return static_cast(error); +} + +ScopedAStatus defaultHwInfo(RpcHardwareInfo* info) { + info->versionNumber = 2; + info->rpcAuthorName = "Google"; + info->supportedEekCurve = RpcHardwareInfo::CURVE_P256; + info->uniqueId = "strongbox keymint"; + return ScopedAStatus::ok(); +} + +uint32_t coseKeyEncodedSize(const std::vector& keysToSign) { + uint32_t size = 0; + for (auto& macKey : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(macKey.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + LOG(ERROR) << "Invalid COSE_Mac0 structure"; + return 0; + } + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + if (!payload) return 0; + size += payload->value().size(); + } + return size; +} + +} // namespace + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RKP_HARDWARE_INFO); + std::optional optVersionNumber; + std::optional optSupportedEekCurve; + std::optional optRpcAuthorName; + std::optional optUniqueId; + if (err != KM_ERROR_OK || !(optVersionNumber = cbor_.getUint64(item, 1)) || + !(optRpcAuthorName = cbor_.getByteArrayStr(item, 2)) || + !(optSupportedEekCurve = cbor_.getUint64(item, 3)) || + !(optUniqueId = cbor_.getByteArrayStr(item, 4))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + info->rpcAuthorName = std::move(optRpcAuthorName.value()); + info->versionNumber = static_cast(std::move(optVersionNumber.value())); + info->supportedEekCurve = static_cast(std::move(optSupportedEekCurve.value())); + info->uniqueId = std::move(optUniqueId.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateEcdsaP256KeyPair( + bool testMode, MacedPublicKey* macedPublicKey, std::vector* privateKeyHandle) { + cppbor::Array array; + array.add(testMode); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_RKP_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + std::optional> optMacedKey; + std::optional> optPKeyHandle; + if (!(optMacedKey = cbor_.getByteArrayVec(item, 1)) || + !(optPKeyHandle = cbor_.getByteArrayVec(item, 2))) { + LOG(ERROR) << "Error in decoding og response in generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *privateKeyHandle = std::move(optPKeyHandle.value()); + macedPublicKey->macedKey = std::move(optMacedKey.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::beginSendData( + bool testMode, const std::vector& keysToSign) { + uint32_t totalEncodedSize = coseKeyEncodedSize(keysToSign); + cppbor::Array array; + array.add(keysToSign.size()); + array.add(totalEncodedSize); + array.add(testMode); + auto [_, err] = card_->sendRequest(Instruction::INS_BEGIN_SEND_DATA_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in beginSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateMacedKey( + const std::vector& keysToSign) { + for (auto& macedPublicKey : keysToSign) { + cppbor::Array array; + array.add(EncodedItem(macedPublicKey.macedKey)); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateMacedKey."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::updateChallenge(const std::vector& challenge) { + Array array; + array.add(challenge); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_CHALLENGE_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateChallenge."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateEEK( + const std::vector& endpointEncCertChain) { + std::vector eekChain = endpointEncCertChain; + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_EEK_CHAIN_CMD, eekChain); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateEEK."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::finishSendData( + std::vector* keysToSignMac, DeviceInfo* deviceInfo, + std::vector& coseEncryptProtectedHeader, cppbor::Map& coseEncryptUnProtectedHeader, + std::vector& partialCipheredData, uint32_t& respFlag) { + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_SEND_DATA_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in finishSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDecodedKeysToSignMac = cbor_.getByteArrayVec(item, 1); + auto optDecodedDeviceInfo = cbor_.getByteArrayVec(item, 2); + auto optCEncryptProtectedHeader = cbor_.getByteArrayVec(item, 3); + auto optCEncryptUnProtectedHeader = cbor_.getMapItem(item, 4); + auto optPCipheredData = cbor_.getByteArrayVec(item, 5); + auto optRespFlag = cbor_.getUint64(item, 6); + if (!optDecodedKeysToSignMac || !optDecodedDeviceInfo || !optCEncryptProtectedHeader || + !optCEncryptUnProtectedHeader || !optPCipheredData || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in finishSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keysToSignMac = std::move(optDecodedKeysToSignMac.value()); + deviceInfo->deviceInfo = std::move(optDecodedDeviceInfo.value()); + coseEncryptProtectedHeader = std::move(optCEncryptProtectedHeader.value()); + coseEncryptUnProtectedHeader = std::move(optCEncryptUnProtectedHeader.value()); + partialCipheredData.insert(partialCipheredData.end(), optPCipheredData->begin(), + optPCipheredData->end()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getResponse(std::vector& partialCipheredData, + cppbor::Array& recepientStructure, + uint32_t& respFlag) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RESPONSE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getResponse."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optPCipheredData = cbor_.getByteArrayVec(item, 1); + auto optArray = cbor_.getArrayItem(item, 2); + auto optRespFlag = cbor_.getUint64(item, 3); + if (!optPCipheredData || !optArray || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in getResponse."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + recepientStructure = std::move(optArray.value()); + partialCipheredData.insert(partialCipheredData.end(), optPCipheredData->begin(), + optPCipheredData->end()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequest( + bool testMode, const std::vector& keysToSign, + const std::vector& endpointEncCertChain, const std::vector& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, std::vector* keysToSignMac) { + std::vector coseEncryptProtectedHeader; + cppbor::Map coseEncryptUnProtectedHeader; + cppbor::Array recipients; + std::vector cipheredData; + uint32_t respFlag; + auto ret = beginSendData(testMode, keysToSign); + if (!ret.isOk()) return ret; + + ret = updateMacedKey(keysToSign); + if (!ret.isOk()) return ret; + + ret = updateChallenge(challenge); + if (!ret.isOk()) return ret; + + ret = updateEEK(endpointEncCertChain); + if (!ret.isOk()) return ret; + + ret = finishSendData(keysToSignMac, deviceInfo, coseEncryptProtectedHeader, + coseEncryptUnProtectedHeader, cipheredData, respFlag); + if (!ret.isOk()) return ret; + + while (respFlag != 0) { // more data is pending to receive + ret = getResponse(cipheredData, recipients, respFlag); + if (!ret.isOk()) return ret; + } + // Create ConseEncrypt structure. + protectedData->protectedData = cppbor::Array() + .add(coseEncryptProtectedHeader) // Protected + .add(std::move(coseEncryptUnProtectedHeader)) // Unprotected + .add(cipheredData) // Payload + .add(std::move(recipients)) + .encode(); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequestV2( + const std::vector& /*keysToSign*/, const std::vector& /*challenge*/, + std::vector* /*csr*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h new file mode 100644 index 0000000..7f41891 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h @@ -0,0 +1,80 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; + +class JavacardRemotelyProvisionedComponentDevice : public BnRemotelyProvisionedComponent { + public: + explicit JavacardRemotelyProvisionedComponentDevice(shared_ptr card) + : card_(card) {} + + virtual ~JavacardRemotelyProvisionedComponentDevice() = default; + + ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override; + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector& keysToSign, + const std::vector& endpointEncCertChain, + const std::vector& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, + std::vector* keysToSignMac) override; + + ScopedAStatus generateCertificateRequestV2(const std::vector& keysToSign, + const std::vector& challenge, + std::vector* csr) override; + + private: + ScopedAStatus beginSendData(bool testMode, const std::vector& keysToSign); + + ScopedAStatus updateMacedKey(const std::vector& keysToSign); + + ScopedAStatus updateChallenge(const std::vector& challenge); + + ScopedAStatus updateEEK(const std::vector& endpointEncCertChain); + + ScopedAStatus finishSendData(std::vector* keysToSignMac, DeviceInfo* deviceInfo, + std::vector& coseEncryptProtectedHeader, + cppbor::Map& coseEncryptUnProtectedHeader, + std::vector& partialCipheredData, uint32_t& respFlag); + + ScopedAStatus getResponse(std::vector& partialCipheredData, + cppbor::Array& recepientStructure, uint32_t& respFlag); + std::shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp new file mode 100644 index 0000000..7c4f038 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" +#include "JavacardSecureElement.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "keymint_utils.h" + +namespace keymint::javacard { + +keymaster_error_t JavacardSecureElement::initializeJavacard() { + Array request; + request.add(Uint(getOsVersion())); + request.add(Uint(getOsPatchlevel())); + request.add(Uint(getVendorPatchlevel())); + auto [item, err] = sendRequest(Instruction::INS_INIT_STRONGBOX_CMD, request); + return err; +} + +keymaster_error_t JavacardSecureElement::sendEarlyBootEndedEvent(bool eventTriggered) { + isEarlyBootEventPending |= eventTriggered; + if (!isEarlyBootEventPending) { + return KM_ERROR_OK; + } + auto [item, err] = sendRequest(Instruction::INS_EARLY_BOOT_ENDED_CMD); + if (err != KM_ERROR_OK) { + // Incase of failure cache the event and send in the next immediate request to Applet. + isEarlyBootEventPending = true; + return err; + } + isEarlyBootEventPending = false; + return KM_ERROR_OK; +} + +keymaster_error_t JavacardSecureElement::constructApduMessage(Instruction& ins, + std::vector& inputData, + std::vector& apduOut) { + apduOut.push_back(static_cast(APDU_CLS)); // CLS + apduOut.push_back(static_cast(ins)); // INS + apduOut.push_back(static_cast(APDU_P1)); // P1 + apduOut.push_back(static_cast(APDU_P2)); // P2 + + if (USHRT_MAX >= inputData.size()) { + // Send extended length APDU always as response size is not known to HAL. + // Case 1: Lc > 0 CLS | INS | P1 | P2 | 00 | 2 bytes of Lc | CommandData | 2 bytes of Le + // all set to 00. Case 2: Lc = 0 CLS | INS | P1 | P2 | 3 bytes of Le all set to 00. + // Extended length 3 bytes, starts with 0x00 + apduOut.push_back(static_cast(0x00)); + if (inputData.size() > 0) { + apduOut.push_back(static_cast(inputData.size() >> 8)); + apduOut.push_back(static_cast(inputData.size() & 0xFF)); + // Data + apduOut.insert(apduOut.end(), inputData.begin(), inputData.end()); + } + // Expected length of output. + // Accepting complete length of output every time. + apduOut.push_back(static_cast(0x00)); + apduOut.push_back(static_cast(0x00)); + } else { + LOG(ERROR) << "Error in constructApduMessage."; + return (KM_ERROR_INVALID_INPUT_LENGTH); + } + return (KM_ERROR_OK); // success +} + +keymaster_error_t JavacardSecureElement::sendData(Instruction ins, std::vector& inData, + std::vector& response) { + keymaster_error_t ret = KM_ERROR_UNKNOWN_ERROR; + std::vector apdu; + + ret = constructApduMessage(ins, inData, apdu); + + if (ret != KM_ERROR_OK) { + return ret; + } + + ret = transport_->sendData(apdu, response); + if (ret != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in sendData. " << static_cast(ret); + return ret; + } + + // Response size should be greater than 2. Cbor output data followed by two bytes of APDU + // status. + if ((response.size() <= 2) || (getApduStatus(response) != APDU_RESP_STATUS_OK)) { + LOG(ERROR) << "Response of the sendData is wrong: response size = " << response.size() + << " apdu status = " << getApduStatus(response); + return (KM_ERROR_UNKNOWN_ERROR); + } + // remove the status bytes + response.pop_back(); + response.pop_back(); + return (KM_ERROR_OK); // success +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, Array& request) { + vector response; + // encode request + std::vector command = request.encode(); + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, std::vector& command) { + vector response; + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins) { + vector response; + vector emptyRequest; + auto sendError = sendData(ins, emptyRequest, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h new file mode 100644 index 0000000..2ea5fe4 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h @@ -0,0 +1,114 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include "CborConverter.h" + +#define APDU_CLS 0x80 +#define APDU_P1 0x50 +#define APDU_P2 0x00 +#define APDU_RESP_STATUS_OK 0x9000 + +#define KEYMINT_CMD_APDU_START 0x20 + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +enum class Instruction { + // Keymaster commands + INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1, + INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2, + INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3, + INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4, + INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5, + INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6, + INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7, + INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8, + INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9, + INS_COMPUTE_SHARED_SECRET_CMD = KEYMINT_CMD_APDU_START + 10, + INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11, + INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12, + INS_GET_SHARED_SECRET_PARAM_CMD = KEYMINT_CMD_APDU_START + 13, + INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14, + INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15, + INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16, + INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17, + INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18, + INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19, + INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20, + INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21, + INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22, + INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23, + INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24, + INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25, + INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26, + // RKP Commands + INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27, + INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28, + INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29, + INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30, + INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31, + INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32, + INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33, + INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34, + // SE ROT Commands + INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45, + INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46, + INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47, +}; + +class JavacardSecureElement { + public: + explicit JavacardSecureElement(shared_ptr transport, uint32_t osVersion, + uint32_t osPatchLevel, uint32_t vendorPatchLevel) + : transport_(transport), osVersion_(osVersion), osPatchLevel_(osPatchLevel), + vendorPatchLevel_(vendorPatchLevel), isEarlyBootEventPending(false) { + transport_->openConnection(); + } + virtual ~JavacardSecureElement() { transport_->closeConnection(); } + + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + std::tuple, keymaster_error_t> sendRequest(Instruction ins); + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + std::vector& command); + + keymaster_error_t sendData(Instruction ins, std::vector& inData, + std::vector& response); + + keymaster_error_t constructApduMessage(Instruction& ins, std::vector& inputData, + std::vector& apduOut); + keymaster_error_t initializeJavacard(); + keymaster_error_t sendEarlyBootEndedEvent(bool eventTriggered); + inline uint16_t getApduStatus(std::vector& inputData) { + // Last two bytes are the status SW0SW1 + uint8_t SW0 = inputData.at(inputData.size() - 2); + uint8_t SW1 = inputData.at(inputData.size() - 1); + return (SW0 << 8 | SW1); + } + + shared_ptr transport_; + uint32_t osVersion_; + uint32_t osPatchLevel_; + uint32_t vendorPatchLevel_; + bool isEarlyBootEventPending; + CborConverter cbor_; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp new file mode 100644 index 0000000..c5cf9a2 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp @@ -0,0 +1,61 @@ +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" +#include "JavacardSharedSecret.h" + +#include + +#include + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::Instruction; + +ScopedAStatus JavacardSharedSecret::getSharedSecretParameters(SharedSecretParameters* params) { + auto error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + auto [item, err] = card_->sendRequest(Instruction::INS_GET_SHARED_SECRET_PARAM_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSSParams = cbor_.getSharedSecretParameters(item, 1); + if (!optSSParams) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *params = std::move(optSSParams.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardSharedSecret::computeSharedSecret(const std::vector& params, + std::vector* secret) { + + auto error = card_->sendEarlyBootEndedEvent(false); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBoot event javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + cppbor::Array request; + cbor_.addSharedSecretParameters(request, params); + auto [item, err] = card_->sendRequest(Instruction::INS_COMPUTE_SHARED_SECRET_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSecret = cbor_.getByteArrayVec(item, 1); + if (!optSecret) { + LOG(ERROR) << "Error in decoding the response in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *secret = std::move(optSecret.value()); + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h new file mode 100644 index 0000000..340853a --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; +using std::vector; + +class JavacardSharedSecret : public BnSharedSecret { + public: + explicit JavacardSharedSecret(shared_ptr card) : card_(card) {} + virtual ~JavacardSharedSecret() {} + + ScopedAStatus getSharedSecretParameters(SharedSecretParameters* params) override; + + ScopedAStatus computeSharedSecret(const std::vector& params, + std::vector* secret) override; + + private: + shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM200/HAL/LICENSE b/ready_se/google/keymint/KM200/HAL/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ready_se/google/keymint/KM200/HAL/METADATA b/ready_se/google/keymint/KM200/HAL/METADATA new file mode 100644 index 0000000..d97975c --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/ready_se/google/keymint/KM200/HAL/OWNERS b/ready_se/google/keymint/KM200/HAL/OWNERS new file mode 100644 index 0000000..0bd972b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OWNERS @@ -0,0 +1,3 @@ +pathakc@google.com +subrahmanyaman@google.com +avinashh@google.com diff --git a/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp b/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp new file mode 100644 index 0000000..3fd5e43 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp @@ -0,0 +1,276 @@ +/* + ** + ** Copyright 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. + */ +#include "OmapiTransport.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::ErrorCode; + +constexpr uint8_t KEYMINT_APPLET_AID[] = {0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, + 0x02, 0x0C, 0x01, 0x01, 0x01}; +std::string const ESE_READER_PREFIX = "eSE"; +constexpr const char omapiServiceName[] = "android.se.omapi.ISecureElementService/default"; + +class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + +keymaster_error_t OmapiTransport::initialize() { + + LOG(DEBUG) << "Initialize the secure element connection"; + + // Get OMAPI vendor stable service handler + ::ndk::SpAIBinder ks2Binder(AServiceManager_checkService(omapiServiceName)); + omapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + + if (omapiSeService == nullptr) { + LOG(ERROR) << "Failed to start omapiSeService null"; + return static_cast(ErrorCode::HARDWARE_NOT_YET_AVAILABLE); + } + + int size = sizeof(KEYMINT_APPLET_AID) / sizeof(KEYMINT_APPLET_AID[0]); + // reset readers, clear readers if already existing + if (mVSReaders.size() > 0) { + closeConnection(); + } + + std::vector readers = {}; + // Get available readers + auto status = omapiSeService->getReaders(&readers); + if (!status.isOk()) { + LOG(ERROR) << "getReaders failed to get available readers: " << status.getMessage(); + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + // Get SE readers handlers + for (auto readerName : readers) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = omapiSeService->getReader(readerName, &reader); + if (!status.isOk()) { + LOG(ERROR) << "getReader for " << readerName.c_str() + << " Failed: " << status.getMessage(); + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + mVSReaders[readerName] = reader; + } + + // Find eSE reader, as of now assumption is only eSE available on device + LOG(DEBUG) << "Finding eSE reader"; + eSEReader = nullptr; + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) { + LOG(DEBUG) << "eSE reader found: " << name; + eSEReader = reader; + break; + } + } + } + + if (eSEReader == nullptr) { + LOG(ERROR) << "secure element reader " << ESE_READER_PREFIX << " not found"; + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + bool isSecureElementPresent = false; + auto res = eSEReader->isSecureElementPresent(&isSecureElementPresent); + if (!res.isOk()) { + eSEReader = nullptr; + LOG(ERROR) << "isSecureElementPresent error: " << res.getMessage(); + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + if (!isSecureElementPresent) { + LOG(ERROR) << "secure element not found"; + eSEReader = nullptr; + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + status = eSEReader->openSession(&session); + if (!status.isOk()) { + LOG(ERROR) << "openSession error: " << status.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + if (session == nullptr) { + LOG(ERROR) << "Could not open session null"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + std::vector aid(KEYMINT_APPLET_AID, KEYMINT_APPLET_AID + size); + auto mSEListener = ndk::SharedRefBase::make(); + status = session->openLogicalChannel(aid, 0x00, mSEListener, &channel); + if (!status.isOk()) { + LOG(ERROR) << "openLogicalChannel error: " << status.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + if (channel == nullptr) { + LOG(ERROR) << "Could not open channel null"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + return KM_ERROR_OK; +} + +bool OmapiTransport::internalTransmitApdu( + std::shared_ptr reader, + std::vector apdu, std::vector& transmitResponse) { + + LOG(DEBUG) << "internalTransmitApdu: trasmitting data to secure element"; + if (reader == nullptr) { + LOG(ERROR) << "eSE reader is null"; + return false; + } + + bool result = true; + auto res = ndk::ScopedAStatus::ok(); + if (session != nullptr) { + res = session->isClosed(&result); + if (!res.isOk()) { + LOG(ERROR) << "isClosed error: " << res.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } + if (result) { + res = reader->openSession(&session); + if (!res.isOk()) { + LOG(ERROR) << "openSession error: " << res.getMessage(); + return false; + } + if (session == nullptr) { + LOG(ERROR) << "Could not open session null"; + return false; + } + } + + result = true; + if (channel != nullptr) { + res = channel->isClosed(&result); + if (!res.isOk()) { + LOG(ERROR) << "isClosed error: " << res.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } + + int size = sizeof(KEYMINT_APPLET_AID) / sizeof(KEYMINT_APPLET_AID[0]); + std::vector aid(KEYMINT_APPLET_AID, KEYMINT_APPLET_AID + size); + if (result) { + auto mSEListener = ndk::SharedRefBase::make(); + res = session->openLogicalChannel(aid, 0x00, mSEListener, &channel); + if (!res.isOk()) { + LOG(ERROR) << "openLogicalChannel error: " << res.getMessage(); + return false; + } + if (channel == nullptr) { + LOG(ERROR) << "Could not open channel null"; + return false; + } + } + + std::vector selectResponse = {}; + res = channel->getSelectResponse(&selectResponse); + if (!res.isOk()) { + LOG(ERROR) << "getSelectResponse error: " << res.getMessage(); + return false; + } + + if ((selectResponse.size() < 2) || + ((selectResponse[selectResponse.size() - 1] & 0xFF) != 0x00) || + ((selectResponse[selectResponse.size() - 2] & 0xFF) != 0x90)) { + LOG(ERROR) << "Failed to select the Applet."; + return false; + } + + res = channel->transmit(apdu, &transmitResponse); + + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + if (!res.isOk()) { + LOG(ERROR) << "transmit error: " << res.getMessage(); + return false; + } + + return true; +} + +keymaster_error_t OmapiTransport::openConnection() { + + // if already conection setup done, no need to initialise it again. + if (isConnected()) { + return KM_ERROR_OK; + } + return initialize(); +} + +keymaster_error_t OmapiTransport::sendData(const vector& inData, vector& output) { + + if (!isConnected()) { + // Try to initialize connection to eSE + LOG(INFO) << "Failed to send data, try to initialize connection SE connection"; + auto res = initialize(); + if (res != KM_ERROR_OK) { + LOG(ERROR) << "Failed to send data, initialization not completed"; + closeConnection(); + return res; + } + } + + if (eSEReader != nullptr) { + LOG(DEBUG) << "Sending apdu data to secure element: " << ESE_READER_PREFIX; + if (internalTransmitApdu(eSEReader, inData, output)) { + return KM_ERROR_OK; + } else { + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } else { + LOG(ERROR) << "secure element reader " << ESE_READER_PREFIX << " not found"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } +} + +keymaster_error_t OmapiTransport::closeConnection() { + LOG(DEBUG) << "Closing all connections"; + if (omapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + mVSReaders.clear(); + } + } + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + return KM_ERROR_OK; +} + +bool OmapiTransport::isConnected() { + // Check already initialization completed or not + if (omapiSeService != nullptr && eSEReader != nullptr) { + LOG(DEBUG) << "Connection initialization already completed"; + return true; + } + + LOG(DEBUG) << "Connection initialization not completed"; + return false; +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/OmapiTransport.h b/ready_se/google/keymint/KM200/HAL/OmapiTransport.h new file mode 100644 index 0000000..a199bbb --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OmapiTransport.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "ITransport.h" + +namespace keymint::javacard { +using std::vector; + +/** + * OmapiTransport is derived from ITransport. This class gets the OMAPI service binder instance and + * uses IPC to communicate with OMAPI service. OMAPI inturn communicates with hardware via + * ISecureElement. + */ +class OmapiTransport : public ITransport { + + public: + OmapiTransport() + : omapiSeService(nullptr), eSEReader(nullptr), session(nullptr), channel(nullptr), + mVSReaders({}) {} + /** + * Gets the binder instance of ISEService, gets te reader corresponding to secure element, + * establishes a session and opens a basic channel. + */ + keymaster_error_t openConnection() override; + /** + * Transmists the data over the opened basic channel and receives the data back. + */ + keymaster_error_t sendData(const vector& inData, vector& output) override; + + /** + * Closes the connection. + */ + keymaster_error_t closeConnection() override; + /** + * Returns the state of the connection status. Returns true if the connection is active, false + * if connection is broken. + */ + bool isConnected() override; + + private: + std::shared_ptr omapiSeService; + std::shared_ptr eSEReader; + std::shared_ptr session; + std::shared_ptr channel; + std::map> + mVSReaders; + keymaster_error_t initialize(); + bool + internalTransmitApdu(std::shared_ptr reader, + std::vector apdu, std::vector& transmitResponse); +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp b/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp new file mode 100644 index 0000000..a3595fe --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp @@ -0,0 +1,144 @@ +/* + ** + ** Copyright 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. + */ +#include "SocketTransport.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include "ITransport.h" + +#define PORT 8080 +#define IPADDR "192.168.9.112" +#define MAX_RECV_BUFFER_SIZE 2500 + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::ErrorCode; +using std::shared_ptr; +using std::vector; + +keymaster_error_t SocketTransport::openConnection() { + struct sockaddr_in serv_addr; + if ((mSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + LOG(ERROR) << "Socket creation failed" + << " Error: " << strerror(errno); + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + // Convert IPv4 and IPv6 addresses from text to binary form + if (inet_pton(AF_INET, IPADDR, &serv_addr.sin_addr) <= 0) { + LOG(ERROR) << "Invalid address/ Address not supported."; + return static_cast(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + if (connect(mSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + close(mSocket); + LOG(ERROR) << "Connection failed. Error: " << strerror(errno); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + socketStatus = true; + return KM_ERROR_OK; +} + +keymaster_error_t SocketTransport::sendData(const vector& inData, + vector& output) { + int count = 1; + while (!socketStatus && count++ < 5) { + sleep(1); + LOG(ERROR) << "Trying to open socket connection... count: " << count; + openConnection(); + } + + if (count >= 5) { + LOG(ERROR) << "Failed to open socket connection"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + // Prepend the input length to the inputData before sending. + vector inDataPrependedLength; + inDataPrependedLength.push_back(static_cast(inData.size() >> 8)); + inDataPrependedLength.push_back(static_cast(inData.size() & 0xFF)); + inDataPrependedLength.insert(inDataPrependedLength.end(), inData.begin(), inData.end()); + + if (0 > + send(mSocket, inDataPrependedLength.data(), inDataPrependedLength.size(), MSG_NOSIGNAL)) { + static int connectionResetCnt = 0; /* To avoid loop */ + if ((ECONNRESET == errno || EPIPE == errno) && connectionResetCnt == 0) { + // Connection reset. Try open socket and then sendData. + socketStatus = false; + connectionResetCnt++; + return sendData(inData, output); + } + LOG(ERROR) << "Failed to send data over socket err: " << errno; + connectionResetCnt = 0; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + if (!readData(output)) { + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + return KM_ERROR_OK; +} + +keymaster_error_t SocketTransport::closeConnection() { + close(mSocket); + socketStatus = false; + return KM_ERROR_OK; +} + +bool SocketTransport::isConnected() { + return socketStatus; +} + +bool SocketTransport::readData(vector& output) { + uint8_t buffer[MAX_RECV_BUFFER_SIZE]; + ssize_t expectedResponseLen = 0; + ssize_t totalBytesRead = 0; + // The first 2 bytes in the response contains the expected response length. + do { + size_t i = 0; + ssize_t numBytes = read(mSocket, buffer, MAX_RECV_BUFFER_SIZE); + if (0 > numBytes) { + LOG(ERROR) << "Failed to read data from socket."; + return false; + } + totalBytesRead += numBytes; + if (expectedResponseLen == 0) { + // First two bytes in the response contains the expected response length. + expectedResponseLen |= static_cast(buffer[1] & 0xFF); + expectedResponseLen |= static_cast((buffer[0] << 8) & 0xFF00); + // 2 bytes for storing the length. + expectedResponseLen += 2; + i = 2; + } + for (; i < numBytes; i++) { + output.push_back(buffer[i]); + } + } while (totalBytesRead < expectedResponseLen); + + return true; +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/SocketTransport.h b/ready_se/google/keymint/KM200/HAL/SocketTransport.h new file mode 100644 index 0000000..73725d0 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/SocketTransport.h @@ -0,0 +1,55 @@ +/* + ** + ** Copyright 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. + */ +#pragma once + +#include +#include + +#include "ITransport.h" + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +class SocketTransport : public ITransport { + + public: + SocketTransport() : mSocket(-1), socketStatus(false) {} + /** + * Creates a socket instance and connects to the provided server IP and port. + */ + keymaster_error_t openConnection() override; + /** + * Sends data over socket and receives data back. + */ + keymaster_error_t sendData(const vector& inData, vector& output) override; + /** + * Closes the connection. + */ + keymaster_error_t closeConnection() override; + /** + * Returns the state of the connection status. Returns true if the connection is active, + * false if connection is broken. + */ + bool isConnected() override; + + private: + bool readData(vector& output); + int mSocket; + bool socketStatus; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml new file mode 100644 index 0000000..c6ca188 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc new file mode 100644 index 0000000..7bb96f0 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc @@ -0,0 +1,3 @@ +service vendor.keymint-strongbox /vendor/bin/hw/android.hardware.security.keymint-service.strongbox + class early_hal + user jc_strongbox diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml new file mode 100644 index 0000000..0631f12 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml @@ -0,0 +1,10 @@ + + + android.hardware.security.keymint + IKeyMintDevice/strongbox + + + android.hardware.security.keymint + IRemotelyProvisionedComponent/strongbox + + diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml new file mode 100644 index 0000000..5492100 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml @@ -0,0 +1,6 @@ + + + android.hardware.security.sharedsecret + ISharedSecret/strongbox + + diff --git a/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp b/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp new file mode 100644 index 0000000..f613eda --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp @@ -0,0 +1,130 @@ +/* + * 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. + */ +#include "keymint_utils.h" + +#include + +#include + +namespace keymint::javacard { + +namespace { + +constexpr char kPlatformVersionProp[] = "ro.build.version.release"; +constexpr char kPlatformVersionRegex[] = "^([0-9]{1,2})(\\.([0-9]{1,2}))?(\\.([0-9]{1,2}))?"; +constexpr size_t kMajorVersionMatch = 1; +constexpr size_t kMinorVersionMatch = 3; +constexpr size_t kSubminorVersionMatch = 5; +constexpr size_t kPlatformVersionMatchCount = kSubminorVersionMatch + 1; + +constexpr char kPlatformPatchlevelProp[] = "ro.build.version.security_patch"; +constexpr char kVendorPatchlevelProp[] = "ro.vendor.build.security_patch"; +constexpr char kPatchlevelRegex[] = "^([0-9]{4})-([0-9]{2})-([0-9]{2})$"; +constexpr size_t kYearMatch = 1; +constexpr size_t kMonthMatch = 2; +constexpr size_t kDayMatch = 3; +constexpr size_t kPatchlevelMatchCount = kDayMatch + 1; + +uint32_t match_to_uint32(const char* expression, const regmatch_t& match) { + if (match.rm_so == -1) return 0; + + size_t len = match.rm_eo - match.rm_so; + std::string s(expression + match.rm_so, len); + return std::stoul(s); +} + +std::string wait_and_get_property(const char* prop) { + std::string prop_value; + while (!::android::base::WaitForPropertyCreation(prop)) + ; + prop_value = ::android::base::GetProperty(prop, "" /* default */); + return prop_value; +} + +uint32_t getOsVersion(const char* version_str) { + regex_t regex; + if (regcomp(®ex, kPlatformVersionRegex, REG_EXTENDED)) { + return 0; + } + + regmatch_t matches[kPlatformVersionMatchCount]; + int not_match = + regexec(®ex, version_str, kPlatformVersionMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t major = match_to_uint32(version_str, matches[kMajorVersionMatch]); + uint32_t minor = match_to_uint32(version_str, matches[kMinorVersionMatch]); + uint32_t subminor = match_to_uint32(version_str, matches[kSubminorVersionMatch]); + + return (major * 100 + minor) * 100 + subminor; +} + +enum class PatchlevelOutput { kYearMonthDay, kYearMonth }; + +uint32_t getPatchlevel(const char* patchlevel_str, PatchlevelOutput detail) { + regex_t regex; + if (regcomp(®ex, kPatchlevelRegex, REG_EXTENDED) != 0) { + return 0; + } + + regmatch_t matches[kPatchlevelMatchCount]; + int not_match = regexec(®ex, patchlevel_str, kPatchlevelMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t year = match_to_uint32(patchlevel_str, matches[kYearMatch]); + uint32_t month = match_to_uint32(patchlevel_str, matches[kMonthMatch]); + + if (month < 1 || month > 12) { + return 0; + } + + switch (detail) { + case PatchlevelOutput::kYearMonthDay: { + uint32_t day = match_to_uint32(patchlevel_str, matches[kDayMatch]); + if (day < 1 || day > 31) { + return 0; + } + return year * 10000 + month * 100 + day; + } + case PatchlevelOutput::kYearMonth: + return year * 100 + month; + } +} + +} // anonymous namespace + +uint32_t getOsVersion() { + std::string version = wait_and_get_property(kPlatformVersionProp); + return getOsVersion(version.c_str()); +} + +uint32_t getOsPatchlevel() { + std::string patchlevel = wait_and_get_property(kPlatformPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonth); +} + +uint32_t getVendorPatchlevel() { + std::string patchlevel = wait_and_get_property(kVendorPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonthDay); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/keymint_utils.h b/ready_se/google/keymint/KM200/HAL/keymint_utils.h new file mode 100644 index 0000000..65cda63 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/keymint_utils.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +// #include + +// namespace aidl::android::hardware::security::keymint { +namespace keymint::javacard { + +using std::vector; + +inline static std::vector blob2vector(const uint8_t* data, const size_t length) { + std::vector result(data, data + length); + return result; +} + +inline static std::vector blob2vector(const std::string& value) { + vector result(reinterpret_cast(value.data()), + reinterpret_cast(value.data()) + value.size()); + return result; +} + +// HardwareAuthToken vector2AuthToken(const vector& buffer); +// vector authToken2vector(const HardwareAuthToken& token); + +uint32_t getOsVersion(); +uint32_t getOsPatchlevel(); +uint32_t getVendorPatchlevel(); + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/service.cpp b/ready_se/google/keymint/KM200/HAL/service.cpp new file mode 100644 index 0000000..e83ee3d --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/service.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.strongbox-service" + +#include + +#include +#include +#include +#include + +#include "JavacardKeyMintDevice.h" +#include "JavacardRemotelyProvisionedComponentDevice.h" +#include "JavacardSecureElement.h" +#include "JavacardSharedSecret.h" +#include "OmapiTransport.h" +#include "SocketTransport.h" +#include "keymint_utils.h" + +using aidl::android::hardware::security::keymint::JavacardKeyMintDevice; +using aidl::android::hardware::security::keymint::JavacardRemotelyProvisionedComponentDevice; +using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::sharedsecret::JavacardSharedSecret; +using keymint::javacard::getOsPatchlevel; +using keymint::javacard::getOsVersion; +using keymint::javacard::getVendorPatchlevel; +using keymint::javacard::ITransport; +using keymint::javacard::JavacardSecureElement; +using keymint::javacard::OmapiTransport; +using keymint::javacard::SocketTransport; + +#define PROP_BUILD_QEMU "ro.kernel.qemu" +#define PROP_BUILD_FINGERPRINT "ro.build.fingerprint" +// Cuttlefish build fingerprint substring. +#define CUTTLEFISH_FINGERPRINT_SS "aosp_cf_" + +template std::shared_ptr addService(Args&&... args) { + std::shared_ptr ser = ndk::SharedRefBase::make(std::forward(args)...); + auto instanceName = std::string(T::descriptor) + "/strongbox"; + LOG(INFO) << "adding javacard strongbox service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} + +std::shared_ptr getTransportInstance() { + bool isEmulator = false; + // Check if the current build is for emulator or device. + isEmulator = android::base::GetBoolProperty(PROP_BUILD_QEMU, false); + if (!isEmulator) { + std::string fingerprint = android::base::GetProperty(PROP_BUILD_FINGERPRINT, ""); + if (!fingerprint.empty()) { + if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0) != std::string::npos) { + isEmulator = true; + } + } + } + + if (!isEmulator) { + return std::make_shared(); + } else { + return std::make_shared(); + } +} + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + // Javacard Secure Element + std::shared_ptr card = std::make_shared( + getTransportInstance(), getOsVersion(), getOsPatchlevel(), getVendorPatchlevel()); + // Add Keymint Service + addService(card); + // Add Shared Secret Service + addService(card); + // Add Remotely Provisioned Component Service + addService(card); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} -- cgit v1.2.3 From 16832bacf38048a0153cc37833629a4051c2443f Mon Sep 17 00:00:00 2001 From: Bob Badour Date: Wed, 8 Mar 2023 10:14:32 -0800 Subject: [LSC] Add LOCAL_LICENSE_KINDS to external/libese Added SPDX-license-identifier-Apache-2.0 to: ready_se/google/keymint/KM200/HAL/Android.bp Bug: 68860345 Bug: 151177513 Bug: 151953481 Test: m all Change-Id: Ie3776fa3a792238df80db7bd25b429472807b409 --- ready_se/google/keymint/KM200/HAL/Android.bp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ready_se/google/keymint/KM200/HAL/Android.bp b/ready_se/google/keymint/KM200/HAL/Android.bp index a5b1768..48dcca7 100644 --- a/ready_se/google/keymint/KM200/HAL/Android.bp +++ b/ready_se/google/keymint/KM200/HAL/Android.bp @@ -13,6 +13,25 @@ // limitations under the License. // +package { + default_applicable_licenses: [ + "external_libese_ready_se_google_keymint_KM200_HAL_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_libese_ready_se_google_keymint_KM200_HAL_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + cc_library { name: "libjc_keymint", defaults: [ -- cgit v1.2.3 From ee23096c0c2bcc42fb1fc3a99f4cc1266d0a9579 Mon Sep 17 00:00:00 2001 From: Tri Vo Date: Mon, 13 Mar 2023 14:00:25 -0700 Subject: Remove KM dependency on RemoteProvisioner HAL implementation shouldn't require an app. Bug: 273325840 Test: m Change-Id: Iddfd2ca124465ec2e811f16816672a1d75b1f45f --- ready_se/google/keymint/KM200/HAL/Android.bp | 1 - 1 file changed, 1 deletion(-) diff --git a/ready_se/google/keymint/KM200/HAL/Android.bp b/ready_se/google/keymint/KM200/HAL/Android.bp index 48dcca7..b05bb3e 100644 --- a/ready_se/google/keymint/KM200/HAL/Android.bp +++ b/ready_se/google/keymint/KM200/HAL/Android.bp @@ -128,7 +128,6 @@ cc_binary { "service.cpp", ], required: [ - "RemoteProvisioner", "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", ], } -- cgit v1.2.3 From b7ca34c9b6a4b2f7ff846aa44dcbcafd16a28372 Mon Sep 17 00:00:00 2001 From: avinashhedage Date: Wed, 11 Jan 2023 09:40:25 +0000 Subject: Added android reay_se implementation of KeyMint 300 Applet Test: run vts -m VtsAidlKeyMintTarget Change-Id: I868b89286761994da3e2ff8a26dfcf0e760315f5 --- .../javacard/keymaster/KMAndroidSEApplet.java | 622 +++ .../javacard/keymaster/KMAttestationCertImpl.java | 1056 ++++ .../javacard/keymaster/KMConfigurations.java | 32 + .../com/android/javacard/keymaster/KMUtils.java | 440 ++ .../com/android/javacard/seprovider/KMAESKey.java | 53 + .../javacard/seprovider/KMAndroidSEProvider.java | 1572 ++++++ .../javacard/seprovider/KMAttestationCert.java | 198 + .../javacard/seprovider/KMDataStoreConstants.java | 17 + .../seprovider/KMECDeviceUniqueKeyPair.java | 55 + .../seprovider/KMEcdsa256NoDigestSignature.java | 138 + .../com/android/javacard/seprovider/KMError.java | 32 + .../android/javacard/seprovider/KMException.java | 46 + .../com/android/javacard/seprovider/KMHmacKey.java | 53 + .../src/com/android/javacard/seprovider/KMKey.java | 10 + .../android/javacard/seprovider/KMKeyObject.java | 10 + .../android/javacard/seprovider/KMOperation.java | 75 + .../javacard/seprovider/KMOperationImpl.java | 415 ++ .../android/javacard/seprovider/KMPoolManager.java | 657 +++ .../seprovider/KMRsa2048NoDigestSignature.java | 140 + .../javacard/seprovider/KMRsaOAEPEncoding.java | 289 ++ .../android/javacard/seprovider/KMSEProvider.java | 780 +++ .../com/android/javacard/seprovider/KMType.java | 79 + .../android/javacard/seprovider/KMUpgradable.java | 30 + ready_se/google/keymint/KM300/Applet/README.md | 16 + .../com/android/javacard/keymaster/KMArray.java | 165 + .../android/javacard/keymaster/KMAsn1Parser.java | 471 ++ .../android/javacard/keymaster/KMBignumTag.java | 110 + .../com/android/javacard/keymaster/KMBoolTag.java | 115 + .../com/android/javacard/keymaster/KMByteBlob.java | 142 + .../com/android/javacard/keymaster/KMByteTag.java | 146 + .../src/com/android/javacard/keymaster/KMCose.java | 513 ++ .../javacard/keymaster/KMCoseCertPayload.java | 136 + .../android/javacard/keymaster/KMCoseHeaders.java | 203 + .../com/android/javacard/keymaster/KMCoseKey.java | 255 + .../com/android/javacard/keymaster/KMCoseMap.java | 171 + .../javacard/keymaster/KMCosePairByteBlobTag.java | 137 + .../javacard/keymaster/KMCosePairCoseKeyTag.java | 89 + .../javacard/keymaster/KMCosePairIntegerTag.java | 92 + .../keymaster/KMCosePairNegIntegerTag.java | 92 + .../keymaster/KMCosePairSimpleValueTag.java | 76 + .../javacard/keymaster/KMCosePairTagType.java | 248 + .../keymaster/KMCosePairTextStringTag.java | 91 + .../com/android/javacard/keymaster/KMDecoder.java | 781 +++ .../com/android/javacard/keymaster/KMEncoder.java | 785 +++ .../src/com/android/javacard/keymaster/KMEnum.java | 166 + .../android/javacard/keymaster/KMEnumArrayTag.java | 305 ++ .../com/android/javacard/keymaster/KMEnumTag.java | 152 + .../com/android/javacard/keymaster/KMError.java | 134 + .../javacard/keymaster/KMHardwareAuthToken.java | 171 + .../keymaster/KMHmacSharingParameters.java | 110 + .../com/android/javacard/keymaster/KMInteger.java | 215 + .../javacard/keymaster/KMIntegerArrayTag.java | 163 + .../android/javacard/keymaster/KMIntegerTag.java | 218 + .../javacard/keymaster/KMKeyCharacteristics.java | 124 + .../javacard/keymaster/KMKeyParameters.java | 472 ++ .../javacard/keymaster/KMKeymasterApplet.java | 5172 ++++++++++++++++++++ .../javacard/keymaster/KMKeymintDataStore.java | 1075 ++++ .../src/com/android/javacard/keymaster/KMMap.java | 209 + .../com/android/javacard/keymaster/KMNInteger.java | 134 + .../javacard/keymaster/KMOperationState.java | 354 ++ .../KMRemotelyProvisionedComponentDevice.java | 1396 ++++++ .../android/javacard/keymaster/KMRepository.java | 130 + .../android/javacard/keymaster/KMSemanticTag.java | 80 + .../android/javacard/keymaster/KMSimpleValue.java | 71 + .../src/com/android/javacard/keymaster/KMTag.java | 102 + .../android/javacard/keymaster/KMTextString.java | 80 + .../src/com/android/javacard/keymaster/KMType.java | 409 ++ .../javacard/keymaster/KMVerificationToken.java | 129 + 68 files changed, 23204 insertions(+) create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java create mode 100644 ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java create mode 100644 ready_se/google/keymint/KM300/Applet/README.md create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java create mode 100644 ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java new file mode 100644 index 0000000..018ac02 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -0,0 +1,622 @@ +/* + * 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" (short)0IS, + * 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.KMAndroidSEProvider; +import com.android.javacard.seprovider.KMException; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.OnUpgradeListener; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class extends from KMKeymasterApplet which is main entry point to receive apdu commands. All + * the provision commands are processed here and later the data is handed over to the KMDataStore + * class which stores the data in the flash memory. + */ +public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { + // Magic number version stored along with provisioned data. This is used to differentiate + // between data before and after the magic number is used. + private static final byte KM_MAGIC_NUMBER = (byte) 0x82; + // MSB byte is for Major version and LSB byte is for Minor version. + public static final short KM_APPLET_PACKAGE_VERSION = 0x0400; + // This flag is used to know if card reset happened. + private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + + // Provider specific Commands + private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; + private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; + // Commands 4, 5 and 6 are reserved for vendor usage. + private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; + // 0x08 was reserved for INS_INIT_STRONGBOX_CMD + // 0x09 was reserved for INS_SET_BOOT_ENDED_CMD earlier. it is unused now. + private static final byte INS_SE_FACTORY_PROVISIONING_LOCK_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 10; + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 11; + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 12; + private static final byte INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 13; + private static final byte INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 14; + private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 15; + private static final byte INS_SET_BOOT_PARAMS_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 16; // Unused + private static final byte INS_OEM_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 17; + private static final byte INS_PROVISION_SECURE_BOOT_MODE_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 18; + + private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; + // The length of the provisioned pre shared key. + public static final byte SHARED_SECRET_KEY_SIZE = 32; + + // Version of the database which is used to differentiate between different version of the + // database. + protected short packageVersion; + + KMAndroidSEApplet() { + super(new KMAndroidSEProvider()); + packageVersion = KM_APPLET_PACKAGE_VERSION; + } + + /** + * Installs this applet. + * + * @param bArray the array containing installation parameters + * @param bOffset the starting offset in bArray + * @param bLength the length in bytes of the parameter data in bArray + */ + public static void install(byte[] bArray, short bOffset, byte bLength) { + new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + public void handleDeviceBooted() { + if (seProvider.isBootSignalEventSupported() && seProvider.isDeviceRebooted()) { + kmDataStore.clearDeviceBootStatus(); + super.reboot(); + seProvider.clearDeviceBooted(true); + } + } + + @Override + public void updateApduStatusFlags(short apduIns) { + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 0; + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 1; + switch (apduIns) { + case INS_GET_PROVISION_STATUS_CMD: + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + super.updateApduStatusFlags(apduIns); + } + } + + @Override + public void process(APDU apdu) { + try { + handleDeviceBooted(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (apduIns == KMType.INVALID_VALUE) { + return; + } + updateApduStatusFlags(apduIns); + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + super.powerReset(); + } + + if (isCommandAllowed(apduIns)) { + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + processProvisionAttestIdsCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ATTEST_IDS); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_PRESHARED_SECRET_CMD: + processProvisionPreSharedSecretCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_PRESHARED_SECRET); + sendResponse(apdu, KMError.OK); + break; + + case INS_GET_PROVISION_STATUS_CMD: + processGetProvisionStatusCmd(apdu); + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + processProvisionRkpDeviceUniqueKeyPair(apdu); + break; + + case INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD: + processProvisionRkpUdsCertChain(apdu); + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + kmDataStore.setProvisionStatus(PROVISION_STATUS_SE_LOCKED); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + processProvisionOEMRootPublicKeyCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_OEM_PUBLIC_KEY); + sendResponse(apdu, KMError.OK); + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + processOEMLockProvisionCmd(apdu); + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + processOEMUnlockProvisionCmd(apdu); + break; + + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + processSecureBootCmd(apdu); + break; + + default: + super.process(apdu); + break; + } + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + } catch (KMException exception) { + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private boolean isCommandAllowed(short apduIns) { + boolean result = true; + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + case INS_PROVISION_PRESHARED_SECRET_CMD: + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + if (kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + if (!kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + if (isSeFactoryProvisioningLocked() || !isSeFactoryProvisioningComplete()) { + result = false; + } + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + // Allow lock only when + // 1. All the necessary provisioning commands are succcessfully executed + // 2. SE provision is locked + // 3. OEM Root Public is provisioned. + if (kmDataStore.isProvisionLocked() + || !(isProvisioningComplete() && isSeFactoryProvisioningLocked())) { + result = false; + } + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + case INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD: + if (isSeFactoryProvisioningLocked()) { + result = false; + } + break; + + case INS_GET_PROVISION_STATUS_CMD: + break; + + default: + // Allow other commands only if provision is completed. + if (!isProvisioningComplete()) { + result = false; + } + } + return result; + } + + private boolean isSeFactoryProvisioningLocked() { + short pStatus = kmDataStore.getProvisionStatus(); + boolean result = false; + if ((0 != (pStatus & PROVISION_STATUS_SE_LOCKED))) { + result = true; + } + return result; + } + + private boolean isSeFactoryProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + if (PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + == (pStatus & PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR)) { + return true; + } + return false; + } + + private void processSecureBootCmd(APDU apdu) { + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); + short args = receiveIncoming(apdu, argsProto); + short val = KMInteger.cast(KMArray.cast(args).get((short) 0)).getShort(); + if (val != 1 && val != 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Store secure boot mode value. + JCSystem.beginTransaction(); + kmDataStore.secureBootMode = (byte) val; + JCSystem.commitTransaction(); + kmDataStore.setProvisionStatus(PROVISION_STATUS_SECURE_BOOT_MODE); + sendResponse(apdu, KMError.OK); + } + + private void processOEMUnlockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_UNLOCK_PROVISION_VERIFICATION_LABEL, apdu); + kmDataStore.unlockProvision(); + sendResponse(apdu, KMError.OK); + } + + private void processOEMLockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_LOCK_PROVISION_VERIFICATION_LABEL, apdu); + // Enable the lock bit in provision status. + kmDataStore.setProvisionStatus(PROVISION_STATUS_PROVISIONING_LOCKED); + sendResponse(apdu, KMError.OK); + } + + private void authenticateOEM(byte[] plainMsg, APDU apdu) { + + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + short args = receiveIncoming(apdu, tmpVariables[0]); + // Get the signature input. + short signature = KMArray.cast(args).get((short) 0); + byte[] oemPublicKey = kmDataStore.getOEMRootPublicKey(); + + if (!seProvider.ecVerify256( + oemPublicKey, + (short) 0, + (short) oemPublicKey.length, + plainMsg, + (short) 0, + (short) plainMsg.length, + KMByteBlob.cast(signature).getBuffer(), + KMByteBlob.cast(signature).getStartOff(), + KMByteBlob.cast(signature).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormatPtr); + KMArray.cast(argsProto).add((short) 2, blob); + short args = receiveIncoming(apdu, argsProto); + + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + // Key format must be RAW format + byte keyFormat = KMEnum.cast(tmpVariables[0]).getVal(); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // get algorithm - only EC keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Purpose should be VERIFY + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.VERIFY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + tmpVariables[0] = KMArray.cast(args).get((short) 2); + // persist OEM Root Public Key. + kmDataStore.persistOEMRootPublicKey( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + } + + private static void processProvisionRkpDeviceUniqueKeyPair(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short arr = KMArray.instance((short) 1); + short coseKeyExp = KMCoseKey.exp(); + KMArray.cast(arr).add((short) 0, coseKeyExp); // [ CoseKey ] + arr = receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.cast(arr).get((short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + // Store the Device unique Key. + kmDataStore.createRkpDeviceUniqueKeyPair( + scratchPad, (short) 0, pubKeyLen, scratchPad, pubKeyLen, privKeyLen); + short dcc = generateDiceCertChain(scratchPad); + short len = KMKeymasterApplet.encodeToApduBuffer(dcc, scratchPad, (short) 0, MAX_COSE_BUF_SIZE); + kmDataStore.persistBootCertificateChain(scratchPad, (short) 0, len); + kmDataStore.setProvisionStatus(PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionRkpUdsCertChain(APDU apdu) { + // X509 certificate chain is received as shown below: + /** + * x509CertChain = bstr .cbor UdsCerts + * + *

UdsCerts = { * SignerName => UdsCertChain } + * + *

; SignerName is a string identifier that indicates both the signing authority as ; well as + * the format of the UdsCertChain SignerName = tstr + * + *

UdsCertChain = [ 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor + * self-signed ; cert, "Leaf" contains UDS_Public. There may also be ; intermediate certificates + * between Root and Leaf. ] + * + *

; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or EdDSA) + * X509Certificate = bstr + */ + // Store the CBOR encoded UdsCerts as it is in the persistent memory so cbor decoding is + // required here. + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + short srcOffset = apdu.getOffsetCdata(); + 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 byteHeaderLen = + decoder.readCertificateChainHeaderLen(buffer, bufferStartOffset, bufferLength); + kmDataStore.persistUdsCertChain( + buffer, + (short) (bufferStartOffset + byteHeaderLen), + (short) (bufferLength - byteHeaderLen)); + kmDataStore.setProvisionStatus(PROVISION_STATUS_UDS_CERT_CHAIN); + // reclaim memory + repository.reclaimMemory(bufferLength); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionAttestIdsCmd(APDU apdu) { + short keyparams = KMKeyParameters.exp(); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, keyparams); + short args = receiveIncoming(apdu, cmd); + + short attData = KMArray.cast(args).get((short) 0); + // persist attestation Ids - if any is missing then exception occurs + setAttestationIds(attData); + } + + public void setAttestationIds(short attIdVals) { + KMKeyParameters instParam = KMKeyParameters.cast(attIdVals); + KMArray vals = KMArray.cast(instParam.getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + + if (KMType.BYTES_TAG != type) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + obj = KMByteTag.cast(obj).getValue(); + if (KMByteBlob.cast(obj).length() > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + kmDataStore.setAttestationId( + key, + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + index++; + } + } + + private void processProvisionPreSharedSecretCmd(APDU apdu) { + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, blob); + short args = receiveIncoming(apdu, argsProto); + + short val = KMArray.cast(args).get((short) 0); + + if (val != KMType.INVALID_VALUE && KMByteBlob.cast(val).length() != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Persist shared Hmac. + kmDataStore.createPresharedKey( + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + } + + // This function masks the error code with POWER_RESET_MASK_FLAG + // in case if card reset event occurred. The clients of the Applet + // has to extract the power reset status from the error code and + // process accordingly. + private static short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + KMInteger.cast(int32Ptr).getStartOff(), + powerResetStatus); + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + (short) (KMInteger.cast(int32Ptr).getStartOff() + 2), + err); + // reset power reset status flag to its default value. + // repository.restorePowerResetStatus(); //TODO + return int32Ptr; + } + + private void processGetProvisionStatusCmd(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + short pStatus = kmDataStore.getProvisionStatus(); + Util.setShort(scratchpad, (short) 0, pStatus); + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); + KMArray.cast(resp).add((short) 1, KMInteger.instance(scratchpad, (short) 0, (short) 2)); + sendOutgoing(apdu, resp); + } + + private boolean isProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + short pCompleteStatus = + PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + | PROVISION_STATUS_PRESHARED_SECRET + | PROVISION_STATUS_ATTEST_IDS + | PROVISION_STATUS_OEM_PUBLIC_KEY + | PROVISION_STATUS_SECURE_BOOT_MODE; + if (kmDataStore.isProvisionLocked() || (pCompleteStatus == (pStatus & pCompleteStatus))) { + return true; + } + return false; + } + + @Override + public void onCleanup() {} + + @Override + public void onConsolidate() {} + + private boolean isUpgradeAllowed(short oldVersion) { + boolean upgradeAllowed = false; + // Downgrade of the Applet is not allowed. + if (KM_APPLET_PACKAGE_VERSION >= oldVersion) { + upgradeAllowed = true; + } + return upgradeAllowed; + } + + @Override + public void onRestore(Element element) { + element.initRead(); + byte magicNumber = element.readByte(); + if (magicNumber != KM_MAGIC_NUMBER) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short oldPackageVersion = element.readShort(); + // Validate version. + if (!isUpgradeAllowed(oldPackageVersion)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + kmDataStore.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + } + + @Override + public Element onSave() { + short primitiveCount = 3; + primitiveCount += kmDataStore.getBackupPrimitiveByteCount(); + short objectCount = kmDataStore.getBackupObjectCount(); + // Create element. + Element element = + UpgradeManager.createElement(Element.TYPE_SIMPLE, primitiveCount, objectCount); + + element.write(KM_MAGIC_NUMBER); + element.write(packageVersion); + kmDataStore.onSave(element); + return element; + } + + private short validateApdu(APDU apdu) { + // Read the apdu header and buffer. + byte[] apduBuffer = apdu.getBuffer(); + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate CLA + if (!apdu.isValidCLA()) { + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + } + + // Validate P1P2. + if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { + sendResponse(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + return apduBuffer[ISO7816.OFFSET_INS]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java new file mode 100644 index 0000000..bf9e7ac --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -0,0 +1,1056 @@ +/* + * 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" (short)0IS, + * 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.KMAESKey; +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * The class encodes strongbox generated and signed attestation certificates. It only encodes the + * required fields of the certificates. This class is not meant to be a generic X509 cert encoder. + * Any fields that are fixed are added as byte arrays. Extensions are encoded as per the values. The + * certificate is assembled with leafs first and then the sequences. + */ +public class KMAttestationCertImpl implements KMAttestationCert { + + // The maximum size of the either software or hardware parameters. + private static final byte MAX_PARAMS = 30; + // DER encoded object identifiers required by the cert. + // rsaEncryption - 1.2.840.113549.1.1.1 + private static final byte[] rsaEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + // ecPublicKey - 1.2.840.10045.2.1 + private static final byte[] eccPubKey = { + 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 + }; + // prime256v1 curve - 1.2.840.10045.3.1.7 + private static final byte[] prime256v1 = { + 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07 + }; + // Key Usage Extn - 2.5.29.15 + private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + // Android Extn - 1.3.6.1.4.1.11129.2.1.17 + private static final byte[] androidExtn = { + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + // The length of the RSA signature. + private static final short RSA_SIG_LEN = 256; + // The maximum length of the ECDSA signature. + private static final byte ECDSA_MAX_SIG_LEN = 72; + // Signature algorithm identifier - ecdsaWithSha256 - 1.2.840.10045.4.3.2 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509EcdsaSignAlgIdentifier = { + 0x30, 0x0A, 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, (byte) 0x3D, 0x04, 0x03, 0x02 + }; + // Signature algorithm identifier - sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509RsaSignAlgIdentifier = { + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 + }; + + // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] swTagIds = { + KMType.ATTESTATION_APPLICATION_ID, + KMType.CREATION_DATETIME, + KMType.ALLOW_WHILE_ON_BODY, + KMType.USAGE_COUNT_LIMIT, + KMType.USAGE_EXPIRE_DATETIME, + KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.ACTIVE_DATETIME, + }; + + // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] hwTagIds = { + KMType.ATTESTATION_ID_SECOND_IMEI, + KMType.BOOT_PATCH_LEVEL, + KMType.VENDOR_PATCH_LEVEL, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_SERIAL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_BRAND, + KMType.OS_PATCH_LEVEL, + KMType.OS_VERSION, + KMType.ROOT_OF_TRUST, + KMType.ORIGIN, + KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.TRUSTED_CONFIRMATION_REQUIRED, + KMType.AUTH_TIMEOUT, + KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, + KMType.EARLY_BOOT_ONLY, + KMType.ROLLBACK_RESISTANCE, + KMType.RSA_OAEP_MGF_DIGEST, + KMType.RSA_PUBLIC_EXPONENT, + KMType.ECCURVE, + KMType.PADDING, + KMType.DIGEST, + KMType.KEYSIZE, + KMType.ALGORITHM, + KMType.PURPOSE + }; + // Below are the constants for the key usage extension. + private static final byte keyUsageSign = (byte) 0x80; // 0 bit + private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit + private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit + private static final byte keyUsageKeyAgreement = (byte) 0x08; // 4th- bit + private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit + // KeyMint HAL Version constant. + private static final short KEYMINT_VERSION = 300; + // Attestation version constant. + private static final short ATTESTATION_VERSION = 300; + // The X.509 version as per rfc5280#section-4.1.2.1 + private static final byte X509_VERSION = (byte) 0x02; + + // Buffer indexes in transient array + private static final byte NUM_INDEX_ENTRIES = 21; + private static final byte CERT_START = (byte) 0; + private static final byte CERT_LENGTH = (byte) 1; + private static final byte TBS_START = (byte) 2; + private static final byte TBS_LENGTH = (byte) 3; + private static final byte BUF_START = (byte) 4; + private static final byte BUF_LENGTH = (byte) 5; + private static final byte SW_PARAM_INDEX = (byte) 6; + private static final byte HW_PARAM_INDEX = (byte) 7; + // Data indexes in transient array + private static final byte STACK_PTR = (byte) 8; + private static final byte UNIQUE_ID = (byte) 9; + private static final byte ATT_CHALLENGE = (byte) 10; + private static final byte NOT_BEFORE = (byte) 11; + private static final byte NOT_AFTER = (byte) 12; + private static final byte PUB_KEY = (byte) 13; + private static final byte VERIFIED_BOOT_KEY = (byte) 14; + private static final byte VERIFIED_HASH = (byte) 15; + private static final byte ISSUER = (byte) 16; + private static final byte SUBJECT_NAME = (byte) 17; + private static final byte SERIAL_NUMBER = (byte) 18; + private static final byte CERT_ATT_KEY_SECRET = (byte) 19; + private static final byte CERT_ATT_KEY_RSA_PUB_MOD = (byte) 20; + // State indexes in transient array + private static final byte NUM_STATE_ENTRIES = 7; + private static final byte KEY_USAGE = (byte) 0; + private static final byte UNUSED_BITS = (byte) 1; + private static final byte DEVICE_LOCKED = (byte) 2; + private static final byte VERIFIED_STATE = (byte) 3; + private static final byte CERT_MODE = (byte) 4; + private static final byte RSA_CERT = (byte) 5; + private static final byte CERT_RSA_SIGN = (byte) 6; + + private static KMAttestationCert inst; + private static KMSEProvider seProvider; + + private static short[] indexes; + private static byte[] states; + + private static byte[] stack; + private static short[] swParams; + private static short[] hwParams; + // The maximum size of the serial number. + private static final byte SERIAL_NUM_MAX_LEN = 20; + + private KMAttestationCertImpl() {} + + public static KMAttestationCert instance(boolean rsaCert, KMSEProvider provider) { + if (inst == null) { + inst = new KMAttestationCertImpl(); + seProvider = provider; + + // Allocate transient memory + indexes = JCSystem.makeTransientShortArray(NUM_INDEX_ENTRIES, JCSystem.CLEAR_ON_RESET); + states = JCSystem.makeTransientByteArray(NUM_STATE_ENTRIES, JCSystem.CLEAR_ON_RESET); + swParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + hwParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + } + init(rsaCert); + return inst; + } + + private static void init(boolean rsaCert) { + for (short i = 0; i < NUM_INDEX_ENTRIES; i++) { + indexes[i] = 0; + } + Util.arrayFillNonAtomic(states, (short) 0, NUM_STATE_ENTRIES, (byte) 0); + stack = null; + states[CERT_MODE] = KMType.NO_CERT; + states[UNUSED_BITS] = 8; + states[RSA_CERT] = rsaCert ? (byte) 1 : (byte) 0; + states[CERT_RSA_SIGN] = 1; + indexes[CERT_ATT_KEY_SECRET] = KMType.INVALID_VALUE; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + indexes[ISSUER] = KMType.INVALID_VALUE; + indexes[SUBJECT_NAME] = KMType.INVALID_VALUE; + indexes[SERIAL_NUMBER] = KMType.INVALID_VALUE; + } + + @Override + public KMAttestationCert verifiedBootHash(short obj) { + indexes[VERIFIED_HASH] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootKey(short obj) { + indexes[VERIFIED_BOOT_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootState(byte val) { + states[VERIFIED_STATE] = val; + return this; + } + + private KMAttestationCert uniqueId(short obj) { + indexes[UNIQUE_ID] = obj; + return this; + } + + @Override + public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad) { + if (!derEncoded) { + // convert milliseconds to UTC date + indexes[NOT_BEFORE] = KMUtils.convertToDate(obj, scratchpad, true); + } else { + indexes[NOT_BEFORE] = + KMByteBlob.instance( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + return this; + } + + @Override + public KMAttestationCert notAfter( + short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad) { + if (!derEncoded) { + if (usageExpiryTimeObj != KMType.INVALID_VALUE) { + // compare if the expiry time is greater then 2050 then use generalized + // time format else use utc time format. + short tmpVar = KMInteger.uint_64(KMUtils.firstJan2050, (short) 0); + if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, false); + } else { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, true); + } + indexes[NOT_AFTER] = usageExpiryTimeObj; + } else { + // notAfter = certExpirtyTimeObj; + } + } else { + indexes[NOT_AFTER] = usageExpiryTimeObj; + } + return this; + } + + @Override + public KMAttestationCert deviceLocked(boolean val) { + if (val) { + states[DEVICE_LOCKED] = (byte) 0xFF; + } else { + states[DEVICE_LOCKED] = 0; + } + return this; + } + + @Override + public KMAttestationCert publicKey(short obj) { + indexes[PUB_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert attestationChallenge(short obj) { + indexes[ATT_CHALLENGE] = obj; + return this; + } + + @Override + public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { + if (hwEnforced) { + hwParams[indexes[HW_PARAM_INDEX]] = tag; + indexes[HW_PARAM_INDEX]++; + } else { + swParams[indexes[SW_PARAM_INDEX]] = tag; + indexes[SW_PARAM_INDEX]++; + } + if (KMTag.getKey(tag) == KMType.PURPOSE) { + createKeyUsage(tag); + } + return this; + } + + @Override + public KMAttestationCert issuer(short obj) { + indexes[ISSUER] = obj; + return this; + } + + private void createKeyUsage(short tag) { + short len = KMEnumArrayTag.cast(tag).length(); + byte index = 0; + while (index < len) { + if (KMEnumArrayTag.cast(tag).get(index) == KMType.SIGN) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageSign); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageDataEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyAgreement); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageCertSign); + } + index++; + } + index = states[KEY_USAGE]; + while (index != 0) { + index = (byte) (index << 1); + states[UNUSED_BITS]--; + } + } + + private static void pushTbsCert(boolean rsaCert, boolean rsa) { + short last = indexes[STACK_PTR]; + pushExtensions(); + // subject public key info + if (rsaCert) { + pushRsaSubjectKeyInfo(); + } else { + pushEccSubjectKeyInfo(); + } + // subject + pushBytes( + KMByteBlob.cast(indexes[SUBJECT_NAME]).getBuffer(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).getStartOff(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).length()); + pushValidity(); + // issuer - der encoded + pushBytes( + KMByteBlob.cast(indexes[ISSUER]).getBuffer(), + KMByteBlob.cast(indexes[ISSUER]).getStartOff(), + KMByteBlob.cast(indexes[ISSUER]).length()); + // Algorithm Id + if (rsa) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + // Serial Number + pushBytes( + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getBuffer(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getStartOff(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + pushIntegerHeader(KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + // Version + pushByte(X509_VERSION); + pushIntegerHeader((short) 1); + pushByte((byte) 0x03); + pushByte((byte) 0xA0); + // Finally sequence header. + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushExtensions() { + short last = indexes[STACK_PTR]; + // Push KeyUsage extension + if (states[KEY_USAGE] != 0) { + pushKeyUsage(states[KEY_USAGE], states[UNUSED_BITS]); + } + if (states[CERT_MODE] == KMType.ATTESTATION_CERT) { + pushKeyDescription(); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // Extensions have explicit tag of [3] + pushLength((short) (last - indexes[STACK_PTR])); + pushByte((byte) 0xA3); + } + + // Time SEQUENCE{UTCTime, UTC or Generalized Time) + private static void pushValidity() { + short last = indexes[STACK_PTR]; + if (indexes[NOT_AFTER] != 0) { + pushBytes( + KMByteBlob.cast(indexes[NOT_AFTER]).getBuffer(), + KMByteBlob.cast(indexes[NOT_AFTER]).getStartOff(), + KMByteBlob.cast(indexes[NOT_AFTER]).length()); + } else { + KMException.throwIt(KMError.INVALID_DATA); + } + pushTimeHeader(KMByteBlob.cast(indexes[NOT_AFTER]).length()); + pushBytes( + KMByteBlob.cast(indexes[NOT_BEFORE]).getBuffer(), + KMByteBlob.cast(indexes[NOT_BEFORE]).getStartOff(), + KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushTimeHeader(KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushTimeHeader(short len) { + if (len == 13) { // UTC Time + pushLength((short) 0x0D); + pushByte((byte) 0x17); + } else if (len == 15) { // Generalized Time + pushLength((short) 0x0F); + pushByte((byte) 0x18); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + + // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public + // exponent + // as positive integer} + private static void pushRsaSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes(KMKeymasterApplet.F4, (short) 0, (short) KMKeymasterApplet.F4.length); + pushIntegerHeader((short) KMKeymasterApplet.F4.length); + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + + // encode modulus as positive if the MSB is 1. + if (KMByteBlob.cast(indexes[PUB_KEY]).get((short) 0) < 0) { + pushByte((byte) 0x00); + pushIntegerHeader((short) (KMByteBlob.cast(indexes[PUB_KEY]).length() + 1)); + } else { + pushIntegerHeader(KMByteBlob.cast(indexes[PUB_KEY]).length()); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushBitStringHeader((byte) 0x00, (short) (last - indexes[STACK_PTR])); + pushRsaEncryption(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} + private static void pushEccSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushBitStringHeader((byte) 0x00, KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushEcDsa(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEcDsa() { + short last = indexes[STACK_PTR]; + pushBytes(prime256v1, (short) 0, (short) prime256v1.length); + pushBytes(eccPubKey, (short) 0, (short) eccPubKey.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushRsaEncryption() { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // KeyDescription ::= SEQUENCE { + // attestationVersion INTEGER, # Value 200 + // attestationSecurityLevel SecurityLevel, # See below + // keymasterVersion INTEGER, # Value 200 + // keymasterSecurityLevel SecurityLevel, # See below + // attestationChallenge OCTET_STRING, # Tag::ATTESTATION_CHALLENGE from attestParams + // uniqueId OCTET_STRING, # Empty unless key has Tag::INCLUDE_UNIQUE_ID + // softwareEnforced AuthorizationList, # See below + // hardwareEnforced AuthorizationList, # See below + // } + private static void pushKeyDescription() { + short last = indexes[STACK_PTR]; + pushHWParams(); + pushSWParams(); + if (indexes[UNIQUE_ID] != 0) { + pushOctetString( + KMByteBlob.cast(indexes[UNIQUE_ID]).getBuffer(), + KMByteBlob.cast(indexes[UNIQUE_ID]).getStartOff(), + KMByteBlob.cast(indexes[UNIQUE_ID]).length()); + } else { + pushOctetStringHeader((short) 0); + } + pushOctetString( + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getBuffer(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getStartOff(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).length()); + pushEnumerated(KMType.STRONGBOX); + pushShort(KEYMINT_VERSION); + pushIntegerHeader((short) 2); + pushEnumerated(KMType.STRONGBOX); + pushShort(ATTESTATION_VERSION); + pushIntegerHeader((short) 2); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(androidExtn, (short) 0, (short) androidExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushSWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) swTagIds.length; + do { + pushParams(swParams, indexes[SW_PARAM_INDEX], swTagIds[index]); + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushHWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) hwTagIds.length; + do { + if (hwTagIds[index] == KMType.ROOT_OF_TRUST) { + pushRoT(); + continue; + } + if (pushParams(hwParams, indexes[HW_PARAM_INDEX], hwTagIds[index])) { + continue; + } + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static boolean pushParams(short[] params, short len, short tagId) { + short index = 0; + while (index < len) { + if (tagId == KMTag.getKey(params[index])) { + pushTag(params[index]); + return true; + } + index++; + } + return false; + } + + private static void pushTag(short tag) { + short type = KMTag.getTagType(tag); + short tagId = KMTag.getKey(tag); + short val; + switch (type) { + case KMType.BYTES_TAG: + val = KMByteTag.cast(tag).getValue(); + pushBytesTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.ENUM_TAG: + val = KMEnumTag.cast(tag).getValue(); + pushEnumTag(tagId, (byte) val); + break; + case KMType.ENUM_ARRAY_TAG: + val = KMEnumArrayTag.cast(tag).getValues(); + pushEnumArrayTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + val = KMIntegerTag.cast(tag).getValue(); + pushIntegerTag( + tagId, + KMInteger.cast(val).getBuffer(), + KMInteger.cast(val).getStartOff(), + KMInteger.cast(val).length()); + break; + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + // According to KeyMint hal only one user secure id is used but this conflicts with + // tag type which is ULONG-REP. Currently this is encoded as SET OF INTEGERS + val = KMIntegerArrayTag.cast(tag).getValues(); + pushIntegerArrayTag(tagId, val); + break; + case KMType.BOOL_TAG: + val = KMBoolTag.cast(tag).getVal(); + pushBoolTag(tagId); + break; + default: + KMException.throwIt(KMError.INVALID_TAG); + break; + } + } + + // RootOfTrust ::= SEQUENCE { + // verifiedBootKey OCTET_STRING, + // deviceLocked BOOLEAN, + // verifiedBootState VerifiedBootState, + // verifiedBootHash OCTET_STRING, + // } + // VerifiedBootState ::= ENUMERATED { + // Verified (0), + // SelfSigned (1), + // Unverified (2), + // Failed (3), + // } + private static void pushRoT() { + short last = indexes[STACK_PTR]; + // verified boot hash + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_HASH]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).length()); + + pushEnumerated(states[VERIFIED_STATE]); + + pushBoolean(states[DEVICE_LOCKED]); + // verified boot Key + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).length()); + + // Finally sequence header + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // ... and tag Id + pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - indexes[STACK_PTR])); + } + + private static void pushOctetString(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + pushOctetStringHeader(len); + } + + private static void pushBoolean(byte val) { + pushByte(val); + pushBooleanHeader((short) 1); + } + + private static void pushBooleanHeader(short len) { + pushLength(len); + pushByte((byte) 0x01); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + short index = 0; + while (index < len) { + pushByte(buf[(short) (start + index)]); + pushIntegerHeader((short) 1); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushIntegerArrayTag(short tagId, short arr) { + short last = indexes[STACK_PTR]; + short index = 0; + short len = KMArray.cast(arr).length(); + short ptr; + while (index < len) { + ptr = KMArray.cast(arr).get(index); + pushInteger( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushSetHeader(short len) { + pushLength(len); + pushByte((byte) 0x31); + } + + private static void pushEnumerated(byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushEnumeratedHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEnumeratedHeader(short len) { + pushLength(len); + pushByte((byte) 0x0A); + } + + private static void pushBoolTag(short tagId) { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushNullHeader() { + pushByte((byte) 0); + pushByte((byte) 0x05); + } + + private static void pushEnumTag(short tagId, byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushInteger(buf, start, len); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Ignore leading zeros. Only Unsigned Integers are required hence if MSB is set then add 0x00 + // as most significant byte. + private static void pushInteger(byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + byte index = 0; + while (index < (byte) len) { + if (buf[(short) (start + index)] != 0) { + break; + } + index++; + } + if (index == (byte) len) { + pushByte((byte) 0x00); + } else { + pushBytes(buf, (short) (start + index), (short) (len - index)); + if (buf[(short) (start + index)] < 0) { // MSB is 1 + pushByte((byte) 0x00); // always unsigned int + } + } + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + } + + // Bytes Tag is a octet string and tag id is added explicitly + private static void pushBytesTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushBytes(buf, start, len); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // tag id <= 30 ---> 0xA0 | {tagId} + // 30 < tagId < 128 ---> 0xBF 0x{tagId} + // tagId >= 128 ---> 0xBF 0x80+(tagId/128) 0x{tagId - (128*(tagId/128))} + private static void pushTagIdHeader(short tagId, short len) { + pushLength(len); + short count = (short) (tagId / 128); + if (count > 0) { + pushByte((byte) (tagId - (128 * count))); + pushByte((byte) (0x80 + count)); + pushByte((byte) 0xBF); + } else if (tagId > 30) { + pushByte((byte) tagId); + pushByte((byte) 0xBF); + } else { + pushByte((byte) (0xA0 | (byte) tagId)); + } + } + + // SEQUENCE {ObjId, OCTET STRING{BIT STRING{keyUsage}}} + private static void pushKeyUsage(byte keyUsage, byte unusedBits) { + short last = indexes[STACK_PTR]; + pushByte(keyUsage); + pushBitStringHeader(unusedBits, (short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushAlgorithmId(byte[] algId) { + pushBytes(algId, (short) 0, (short) algId.length); + } + + private static void pushIntegerHeader(short len) { + pushLength(len); + pushByte((byte) 0x02); + } + + private static void pushOctetStringHeader(short len) { + pushLength(len); + pushByte((byte) 0x04); + } + + private static void pushSequenceHeader(short len) { + pushLength(len); + pushByte((byte) 0x30); + } + + private static void pushBitStringHeader(byte unusedBits, short len) { + pushByte(unusedBits); + pushLength((short) (len + 1)); // 1 extra byte for unused bits byte + pushByte((byte) 0x03); + } + + private static void pushLength(short len) { + if (len < 128) { + pushByte((byte) len); + } else if (len < 256) { + pushByte((byte) len); + pushByte((byte) 0x81); + } else { + pushShort(len); + pushByte((byte) 0x82); + } + } + + private static void pushShort(short val) { + decrementStackPtr((short) 2); + Util.setShort(stack, indexes[STACK_PTR], val); + } + + private static void pushByte(byte val) { + decrementStackPtr((short) 1); + stack[indexes[STACK_PTR]] = val; + } + + private static void pushBytes(byte[] buf, short start, short len) { + decrementStackPtr(len); + if (buf != null) { + Util.arrayCopyNonAtomic(buf, start, stack, indexes[STACK_PTR], len); + } + } + + private static void decrementStackPtr(short cnt) { + indexes[STACK_PTR] = (short) (indexes[STACK_PTR] - cnt); + if (indexes[BUF_START] > indexes[STACK_PTR]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + @Override + public KMAttestationCert buffer(byte[] buf, short start, short maxLen) { + stack = buf; + indexes[BUF_START] = start; + indexes[BUF_LENGTH] = maxLen; + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + return this; + } + + @Override + public short getCertStart() { + return indexes[CERT_START]; + } + + @Override + public short getCertLength() { + return indexes[CERT_LENGTH]; + } + + public void build(short attSecret, short attMod, boolean rsaSign, boolean fakeCert) { + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + short last = indexes[STACK_PTR]; + short sigLen = 0; + if (fakeCert) { + rsaSign = true; + pushByte((byte) 0); + sigLen = 1; + } + // Push placeholder signature Bit string header + // This will potentially change at the end + else if (rsaSign) { + decrementStackPtr(RSA_SIG_LEN); + } else { + decrementStackPtr(ECDSA_MAX_SIG_LEN); + } + short signatureOffset = indexes[STACK_PTR]; + pushBitStringHeader((byte) 0, (short) (last - indexes[STACK_PTR])); + if (rsaSign) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + indexes[TBS_LENGTH] = indexes[STACK_PTR]; + pushTbsCert((states[RSA_CERT] == 0 ? false : true), rsaSign); + indexes[TBS_START] = indexes[STACK_PTR]; + indexes[TBS_LENGTH] = (short) (indexes[TBS_LENGTH] - indexes[TBS_START]); + if (attSecret != KMType.INVALID_VALUE) { + // Sign with the attestation key + // The pubKey is the modulus. + if (rsaSign) { + sigLen = + seProvider.rsaSign256Pkcs1( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + KMByteBlob.cast(attMod).getBuffer(), + KMByteBlob.cast(attMod).getStartOff(), + KMByteBlob.cast(attMod).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > RSA_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } else { + sigLen = + seProvider.ecSign256( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Adjust signature length + indexes[STACK_PTR] = signatureOffset; + pushBitStringHeader((byte) 0, sigLen); + } else if (!fakeCert) { // No attestation key provisioned in the factory + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } + last = (short) (signatureOffset + sigLen); + // Add certificate sequence header + indexes[STACK_PTR] = indexes[TBS_START]; + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + indexes[CERT_START] = indexes[STACK_PTR]; + indexes[CERT_LENGTH] = (short) (last - indexes[CERT_START]); + } + + @Override + public void build() { + if (states[CERT_MODE] == KMType.FAKE_CERT) { + build(KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); + } else { + build( + indexes[CERT_ATT_KEY_SECRET], + indexes[CERT_ATT_KEY_RSA_PUB_MOD], + (states[CERT_RSA_SIGN] == 0 ? false : true), + false); + } + } + + @Override + public KMAttestationCert makeUniqueId( + byte[] scratchPad, + short scratchPadOff, + byte[] creationTime, + short timeOffset, + short creationTimeLen, + byte[] attestAppId, + short appIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey) { + // Concatenate T||C||R + // temporal count T + short temp = + KMUtils.countTemporalCount( + creationTime, timeOffset, creationTimeLen, scratchPad, scratchPadOff); + Util.setShort(scratchPad, (short) scratchPadOff, temp); + temp = scratchPadOff; + scratchPadOff += 2; + + // Application Id C + Util.arrayCopyNonAtomic(attestAppId, appIdOff, scratchPad, scratchPadOff, attestAppIdLen); + scratchPadOff += attestAppIdLen; + + // Reset After Rotation R + scratchPad[scratchPadOff] = resetSinceIdRotation; + scratchPadOff++; + + // Get the key data from the master key + KMAESKey aesKey = (KMAESKey) masterKey; + short mKeyData = KMByteBlob.instance((short) (aesKey.aesKey.getSize() / 8)); + aesKey.aesKey.getKey( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ + timeOffset = KMByteBlob.instance((short) 32); + appIdOff = + seProvider.hmacSign( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff(), /* Key start*/ + KMByteBlob.cast(mKeyData).length(), /* Key length*/ + scratchPad, /* data */ + temp, /* data start */ + scratchPadOff, /* data length */ + KMByteBlob.cast(timeOffset).getBuffer(), /* signature buffer */ + KMByteBlob.cast(timeOffset).getStartOff()); /* signature start */ + if (appIdOff != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return uniqueId(timeOffset); + } + + @Override + public boolean serialNumber(short number) { + // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 + short length = KMByteBlob.cast(number).length(); + if (length > SERIAL_NUM_MAX_LEN) { + return false; + } + // The serial number Must be a positive integer. + byte msb = KMByteBlob.cast(number).get((short) 0); + if (msb < 0 && length > (SERIAL_NUM_MAX_LEN - 1)) { + return false; + } + indexes[SERIAL_NUMBER] = number; + return true; + } + + @Override + public boolean subjectName(short sub) { + if (sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; + indexes[SUBJECT_NAME] = sub; + return true; + } + + @Override + public KMAttestationCert ecAttestKey(short attestKey, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestKey; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + states[CERT_RSA_SIGN] = 0; + return this; + } + + @Override + public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestPrivExp; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = attestMod; + states[CERT_RSA_SIGN] = 1; + return this; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java new file mode 100644 index 0000000..b7a66d2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * This class contains all the configuration values. Vendors can modify these values accordingly + * based on their environment. + */ +public class KMConfigurations { + // Machine types + public static final byte LITTLE_ENDIAN = 0x00; + public static final byte BIG_ENDIAN = 0x01; + public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN; + // If the size of the attestation ids is known and lesser than 64 + // then reduce the size here. It reduces the heap memory usage. + public static final byte MAX_ATTESTATION_IDS_SIZE = 64; + // DER subject max length. + public static final short MAX_SUBJECT_DER_LEN = 1095; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java new file mode 100644 index 0000000..95ee67f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -0,0 +1,440 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.Util; + +/** + * This is a utility class which helps in converting date to UTC format and doing some arithmetic + * Operations. + */ +public class KMUtils { + + // 64 bit unsigned calculations for time + public static final byte[] oneSecMsec = {0, 0, 0, 0, 0, 0, 0x03, (byte) 0xE8}; // 1000 msec + public static final byte[] oneMinMsec = {0, 0, 0, 0, 0, 0, (byte) 0xEA, 0x60}; // 60000 msec + public static final byte[] oneHourMsec = { + 0, 0, 0, 0, 0, 0x36, (byte) 0xEE, (byte) 0x80 + }; // 3600000 msec + public static final byte[] oneDayMsec = {0, 0, 0, 0, 0x05, 0x26, 0x5C, 0x00}; // 86400000 msec + public static final byte[] oneMonthMsec = { + 0, 0, 0, 0, (byte) 0x9C, (byte) 0xBE, (byte) 0xBD, 0x50 + }; // 2629746000 msec + public static final byte[] leapYearMsec = { + 0, 0, 0, 0x07, (byte) 0x5C, (byte) 0xD7, (byte) 0x88, 0x00 + }; // 31622400000; + public static final byte[] yearMsec = { + 0, 0, 0, 0x07, 0x57, (byte) 0xB1, 0x2C, 0x00 + }; // 31536000000 + // Leap year(366) + 3 * 365 + public static final byte[] fourYrsMsec = { + 0, 0, 0, 0x1D, 0x63, (byte) 0xEB, 0x0C, 0x00 + }; // 126230400000 + public static final byte[] firstJan2020 = { + 0, 0, 0x01, 0x6F, 0x5E, 0x66, (byte) 0xE8, 0x00 + }; // 1577836800000 msec + public static final byte[] firstJan2050 = { + 0, 0, 0x02, 0x4b, (byte) 0xCE, 0x5C, (byte) 0xF0, 0x00 + }; // 2524608000000 + // msec + public static final byte[] febMonthLeapMSec = { + 0, 0, 0, 0, (byte) 0x95, 0x58, 0x6C, 0x00 + }; // 2505600000 + public static final byte[] febMonthMsec = { + 0, 0, 0, 0, (byte) 0x90, 0x32, 0x10, 0x00 + }; // 2419200000 + public static final byte[] ThirtyOneDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9F, (byte) 0xA5, 0x24, 0x00 + }; // 2678400000 + public static final byte[] ThirtDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9A, 0x7E, (byte) 0xC8, 0x00 + }; // 2592000000 + public static final short year2051 = 2051; + public static final short year2020 = 2020; + // Convert to milliseconds constants + public static final byte[] SEC_TO_MILLIS_SHIFT_POS = {9, 8, 7, 6, 5, 3}; + + // -------------------------------------- + public static short convertToDate(short time, byte[] scratchPad, boolean utcFlag) { + + short yrsCount = 0; + short monthCount = 1; + short dayCount = 1; + short hhCount = 0; + short mmCount = 0; + short ssCount = 0; + byte Z = 0x5A; + boolean from2020 = true; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(time).length()), + KMInteger.cast(time).length()); + // If the time is less then 1 Jan 2020 then it is an error + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2020, (short) 0, (short) 8) + < 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (utcFlag + && KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + >= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + < 0) { + Util.arrayCopyNonAtomic(firstJan2020, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + from2020 = false; + Util.arrayCopyNonAtomic(firstJan2050, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + // divide the given time with four yrs msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, fourYrsMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(fourYrsMsec, (short) 0, scratchPad, (short) 8, (short) 8); + // quotient is multiple of 4 + yrsCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + yrsCount = (short) (yrsCount * 4); // number of yrs. + // copy reminder as new dividend + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Get the leap year index starting from the (base Year + yrsCount) Year. + short leapYrIdx = getLeapYrIndex(from2020, yrsCount); + + // if leap year index is 0, then the number of days for the 1st year will be 366 days. + // if leap year index is not 0, then the number of days for the 1st year will be 365 days. + if (((leapYrIdx == 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + >= 0)) + || ((leapYrIdx != 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + >= 0))) { + for (short i = 0; i < 4; i++) { + yrsCount++; + if (i == leapYrIdx) { + Util.arrayCopyNonAtomic(leapYearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + Util.arrayCopyNonAtomic(yearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + if (((short) (i + 1) == leapYrIdx)) { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } else { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } + } + } + + // total yrs from 1970 + if (from2020) { + yrsCount = (short) (year2020 + yrsCount); + } else { + yrsCount = (short) (year2051 + yrsCount); + } + + // divide the given time with one month msec count + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, oneMonthMsec, (short) 0, (short) 8) + >= 0) { + for (short i = 0; i < 12; i++) { + if (i == 1) { + // Feb month + if (isLeapYear(yrsCount)) { + // Leap year 29 days + Util.arrayCopyNonAtomic(febMonthLeapMSec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 28 days + Util.arrayCopyNonAtomic(febMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + } else if (((i <= 6) && ((i % 2 == 0))) || ((i > 6) && ((i % 2 == 1)))) { + Util.arrayCopyNonAtomic( + ThirtyOneDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 30 Days + Util.arrayCopyNonAtomic(ThirtDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, scratchPad, (short) 8, (short) 8) + >= 0) { + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + break; + } + monthCount++; + } + } + + // divide the given time with one day msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneDayMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneDayMsec, (short) 0, scratchPad, (short) 8, (short) 8); + dayCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + dayCount++; + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one hour msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneHourMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneHourMsec, (short) 0, scratchPad, (short) 8, (short) 8); + hhCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one minute msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneMinMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneMinMsec, (short) 0, scratchPad, (short) 8, (short) 8); + mmCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one second msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneSecMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneSecMsec, (short) 0, scratchPad, (short) 8, (short) 8); + ssCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Now convert to ascii string YYMMDDhhmmssZ or YYYYMMDDhhmmssZ + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = numberToString(yrsCount, scratchPad, (short) 0); // returns YYYY + len += numberToString(monthCount, scratchPad, len); + len += numberToString(dayCount, scratchPad, len); + len += numberToString(hhCount, scratchPad, len); + len += numberToString(mmCount, scratchPad, len); + len += numberToString(ssCount, scratchPad, len); + scratchPad[len] = Z; + len++; + if (utcFlag) { + return KMByteBlob.instance(scratchPad, (short) 2, (short) (len - 2)); // YY + } else { + return KMByteBlob.instance(scratchPad, (short) 0, len); // YYYY + } + } + + public static short numberToString(short number, byte[] scratchPad, short offset) { + byte zero = 0x30; + byte len = 2; + byte digit; + if (number > 999) { + len = 4; + } + byte index = len; + while (index > 0) { + digit = (byte) (number % 10); + number = (short) (number / 10); + scratchPad[(short) (offset + index - 1)] = (byte) (digit + zero); + index--; + } + return len; + } + + // Use Euclid's formula: dividend = quotient*divisor + remainder + // i.e. dividend - quotient*divisor = remainder where remainder < divisor. + // so this is division by subtraction until remainder remains. + public static short divide(byte[] buf, short dividend, short divisor, short remainder) { + short expCnt = 1; + short q = 0; + // first increase divisor so that it becomes greater then dividend. + while (compare(buf, divisor, dividend) < 0) { + shiftLeft(buf, divisor); + expCnt = (short) (expCnt << 1); + } + // Now subtract divisor from dividend if dividend is greater then divisor. + // Copy remainder in the dividend and repeat. + while (expCnt != 0) { + if (compare(buf, dividend, divisor) >= 0) { + subtract(buf, dividend, divisor, remainder, (byte) 8); + copy(buf, remainder, dividend); + q = (short) (q + expCnt); + } + expCnt = (short) (expCnt >> 1); + shiftRight(buf, divisor); + } + return q; + } + + public static void copy(byte[] buf, short from, short to) { + Util.arrayCopyNonAtomic(buf, from, buf, to, (short) 8); + } + + public static byte compare(byte[] buf, short lhs, short rhs) { + return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); + } + + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + + public static void shiftLeft(byte[] buf, short start) { + byte index = 7; + byte carry = 0; + byte tmp; + while (index >= 0) { + tmp = buf[(short) (start + index)]; + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] << 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] + carry); + if (tmp < 0) { + carry = 1; + } else { + carry = 0; + } + index--; + } + } + + public static void shiftRight(byte[] buf, short start) { + byte index = 0; + byte carry = 0; + byte tmp; + while (index < 8) { + tmp = (byte) (buf[(short) (start + index)] & 0x01); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] >> 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] & 0x7F); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] | carry); + if (tmp == 1) { + carry = (byte) 0x80; + } else { + carry = 0; + } + index++; + } + } + + public static void add(byte[] buf, short op1, short op2, short result) { + byte index = 7; + byte carry = 0; + short tmp; + short val1 = 0; + short val2 = 0; + while (index >= 0) { + val1 = (short) (buf[(short) (op1 + index)] & 0x00FF); + val2 = (short) (buf[(short) (op2 + index)] & 0x00FF); + tmp = (short) (val1 + val2 + carry); + carry = 0; + if (tmp > 255) { + carry = 1; // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); + index--; + } + } + + // subtraction by borrowing. + public static void subtract(byte[] buf, short op1, short op2, short result, byte sizeBytes) { + byte borrow = 0; + byte index = (byte) (sizeBytes - 1); + short r; + short x; + short y; + while (index >= 0) { + x = (short) (buf[(short) (op1 + index)] & 0xFF); + y = (short) (buf[(short) (op2 + index)] & 0xFF); + r = (short) (x - y - borrow); + borrow = 0; + if (r < 0) { + borrow = 1; + r = (short) (r + 256); // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (r & 0xFF); + index--; + } + } + + public static short countTemporalCount( + byte[] bufTime, short timeOff, short timeLen, byte[] scratchPad, short offset) { + Util.arrayFillNonAtomic(scratchPad, (short) offset, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic(bufTime, timeOff, scratchPad, (short) (offset + 8 - timeLen), timeLen); + Util.arrayCopyNonAtomic( + ThirtDaysMonthMsec, (short) 0, scratchPad, (short) (offset + 8), (short) 8); + return divide(scratchPad, (short) 0, (short) 8, (short) 16); + } + + public static boolean isLeapYear(short year) { + if ((short) (year % 4) == (short) 0) { + if (((short) (year % 100) == (short) 0) && ((short) (year % 400)) != (short) 0) { + return false; + } + return true; + } + return false; + } + + public static short getLeapYrIndex(boolean from2020, short yrsCount) { + short newBaseYr = (short) (from2020 ? (year2020 + yrsCount) : (year2051 + yrsCount)); + for (short i = 0; i < 4; i++) { + if (isLeapYear((short) (newBaseYr + i))) { + return i; + } + } + return -1; + } + + public static void computeOnesCompliment(byte[] buf, short offset, short len) { + short index = offset; + // Compute 1s compliment + while (index < (short) (len + offset)) { + buf[index] = (byte) ~buf[index]; + index++; + } + } + + // i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds( + byte[] buf, short inputOff, short outputOff, short scratchPadOff) { + short index = 0; + short length = (short) SEC_TO_MILLIS_SHIFT_POS.length; + while (index < length) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, SEC_TO_MILLIS_SHIFT_POS[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java new file mode 100644 index 0000000..41059bb --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java @@ -0,0 +1,53 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.AESKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for AESKey. */ +public class KMAESKey implements KMKey { + + public AESKey aesKey; + + public KMAESKey(AESKey key) { + aesKey = key; + } + + public static void onSave(Element element, KMAESKey kmKey) { + element.write(kmKey.aesKey); + } + + public static KMAESKey onRestore(AESKey aesKey) { + if (aesKey == null) { + return null; + } + return new KMAESKey(aesKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java new file mode 100644 index 0000000..b8e78a0 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -0,0 +1,1572 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class implements KMSEProvider and provides all the necessary crypto operations required to + * support the KeyMint specification. This class supports AES, 3DES, HMAC, RSA, ECDSA, ECDH + * algorithms additionally it also supports ECDSA_NO_DIGEST, RSA_NO_DIGEST and RSA_OAEP_MGF1_SHA1 + * and RSA_OAEP_MGF1_SHA256 algorithms. This class follows the pattern of Init-Update-Final for the + * crypto operations. + */ +public class KMAndroidSEProvider implements KMSEProvider { + + // The tag length for AES GCM algorithm. + public static final byte AES_GCM_TAG_LENGTH = 16; + // The nonce length for AES GCM algorithm. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // AES keysize offsets in aesKeys[] for 128 and 256 sizes respectively. + public static final byte KEYSIZE_128_OFFSET = 0x00; + public static final byte KEYSIZE_256_OFFSET = 0x01; + // The size of the temporary buffer. + public static final short TMP_ARRAY_SIZE = 300; + // The length of the rsa key in bytes. + private static final short RSA_KEY_SIZE = 256; + // Below are the flag to denote device reset events + public static final byte POWER_RESET_FALSE = (byte) 0xAA; + public static final byte POWER_RESET_TRUE = (byte) 0x00; + // The computed HMAC key size. + private static final byte COMPUTED_HMAC_KEY_SIZE = 32; + // The constant 'L' as defiend in + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf, page 12. + private static byte[] CMAC_KDF_CONSTANT_L; + // Constant to represent 0. + private static byte[] CMAC_KDF_CONSTANT_ZERO; + // KeyAgreement instance. + private static KeyAgreement keyAgreement; + + // AESKey + private AESKey aesKeys[]; + // DES3Key + private DESKey triDesKey; + // HMACKey + private HMACKey hmacKey; + // RSA Key Pair + private KeyPair rsaKeyPair; + // EC Key Pair. + private KeyPair ecKeyPair; + // Temporary array. + public byte[] tmpArray; + // This is used for internal encryption/decryption operations. + private static AEADCipher aesGcmCipher; + // Instance of Signature algorithm used in KDF. + private Signature kdf; + // Flag used to denote the power reset event. + public static byte[] resetFlag; + // Instance of HMAC Signature algorithm. + private Signature hmacSignature; + // For ImportwrappedKey operations. + private KMRsaOAEPEncoding rsaOaepDecipher; + // Instance of pool manager. + private KMPoolManager poolMgr; + // Instance of KMOperationImpl used only to encrypt/decrypt the KeyBlobs. + private KMOperationImpl globalOperation; + // Entropy + private RandomData rng; + // Singleton instance. + private static KMAndroidSEProvider androidSEProvider = null; + + public static KMAndroidSEProvider getInstance() { + return androidSEProvider; + } + + public KMAndroidSEProvider() { + initStatics(); + // Re-usable AES,DES and HMAC keys in persisted memory. + aesKeys = new AESKey[2]; + aesKeys[KEYSIZE_128_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + aesKeys[KEYSIZE_256_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + triDesKey = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + hmacKey = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); + poolMgr = KMPoolManager.getInstance(); + poolMgr.initECKey(ecKeyPair); + // RsaOAEP Decipher + rsaOaepDecipher = new KMRsaOAEPEncoding(KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1); + + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + + globalOperation = new KMOperationImpl(); + + // Temporary transient array created to use locally inside functions. + tmpArray = JCSystem.makeTransientByteArray(TMP_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + // Random number generator initialisation. + rng = RandomData.getInstance(RandomData.ALG_KEYGENERATION); + androidSEProvider = this; + resetFlag = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET); + resetFlag[0] = (byte) POWER_RESET_FALSE; + } + + void initStatics() { + CMAC_KDF_CONSTANT_L = new byte[] {0x00, 0x00, 0x01, 0x00}; + CMAC_KDF_CONSTANT_ZERO = new byte[] {0x00}; + } + + public void clean() { + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + } + + public AESKey createAESKey(short keysize) { + try { + if (keysize > TMP_ARRAY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createAESKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public AESKey createAESKey(byte[] buf, short startOff, short length) { + AESKey key = null; + short keysize = (short) (length * 8); + if (keysize == 128) { + key = (AESKey) aesKeys[KEYSIZE_128_OFFSET]; + key.setKey(buf, (short) startOff); + } else if (keysize == 256) { + key = (AESKey) aesKeys[KEYSIZE_256_OFFSET]; + key.setKey(buf, (short) startOff); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + return key; + } + + public DESKey createTDESKey() { + try { + newRandomNumber(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + return createTDESKey(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + } finally { + clean(); + } + } + + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + public HMACKey createHMACKey(short keysize) { + // As per the KeyMint2.0 specification + // The minimum supported HMAC key size is 64 bits + // The maximum supported HMAC key size is 512 bits + // The keysize should be a multiple of 8. + if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + try { + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createHMACKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + hmacKey.setKey(secretBuffer, secretOff, secretLength); + return hmacKey; + } + + public KeyPair createRsaKeyPair() { + rsaKeyPair.genKeyPair(); + return rsaKeyPair; + } + + public RSAPrivateKey createRsaKey( + byte[] modBuffer, + short modOff, + short modLength, + byte[] privBuffer, + short privOff, + short privLength) { + RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + } + + public KeyPair createECKeyPair() { + ecKeyPair.genKeyPair(); + return ecKeyPair; + } + + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + privKey.setS(privBuffer, privOff, privLength); + return privKey; + } + + @Override + public short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + switch (alg) { + case KMType.AES: + AESKey aesKey = createAESKey(keysize); + return aesKey.getKey(buf, startOff); + case KMType.DES: + DESKey desKey = createTDESKey(); + return desKey.getKey(buf, startOff); + case KMType.HMAC: + HMACKey hmacKey = createHMACKey(keysize); + return hmacKey.getKey(buf, startOff); + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return 0; + } + + @Override + public void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength, + short[] lengths) { + switch (alg) { + case KMType.RSA: + if (RSA_KEY_SIZE != privKeyLength || RSA_KEY_SIZE != pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KeyPair rsaKey = createRsaKeyPair(); + RSAPrivateKey privKey = (RSAPrivateKey) rsaKey.getPrivate(); + // Copy exponent. + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[0] = privKey.getExponent(tmpArray, (short) 0); + if (lengths[0] > privKeyLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + privKeyBuf, + (short) (privKeyStart + privKeyLength - lengths[0]), + lengths[0]); + // Copy modulus + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[1] = privKey.getModulus(tmpArray, (short) 0); + if (lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + pubModBuf, + (short) (pubModStart + pubModLength - lengths[1]), + lengths[1]); + break; + case KMType.EC: + KeyPair ecKey = createECKeyPair(); + ECPublicKey ecPubKey = (ECPublicKey) ecKey.getPublic(); + ECPrivateKey ecPrivKey = (ECPrivateKey) ecKey.getPrivate(); + lengths[0] = ecPrivKey.getS(privKeyBuf, privKeyStart); + lengths[1] = ecPubKey.getW(pubModBuf, pubModStart); + if (lengths[0] > privKeyLength || lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public boolean importSymmetricKey( + byte alg, short keysize, byte[] buf, short startOff, short length) { + switch (alg) { + case KMType.AES: + createAESKey(buf, startOff, length); + break; + case KMType.DES: + createTDESKey(buf, startOff, length); + break; + case KMType.HMAC: + createHMACKey(buf, startOff, length); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + switch (alg) { + case KMType.RSA: + createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, privKeyStart, privKeyLength); + break; + case KMType.EC: + createEcKey(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public void getTrueRandomNumber(byte[] buf, short start, short length) { + newRandomNumber(buf, start, length); + } + + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + rng.nextBytes(num, startOff, length); + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + rng.setSeed(num, offset, length); + } + + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (authTagLen != AES_GCM_TAG_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (nonceLen != AES_GCM_NONCE_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart); + aesGcmCipher.retrieveTag(authTag, authTagStart, authTagLen); + return ciphLen; + } + + @Override + public short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + return aesGCMEncrypt( + key, + secret, + secretStart, + secretLen, + encSecret, + encSecretStart, + nonce, + nonceStart, + nonceLen, + authData, + authDataStart, + authDataLen, + authTag, + authTagStart, + authTagLen); + } + + @Override + public boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + boolean verification = false; + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + // encrypt the secret + aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart); + verification = + aesGcmCipher.verifyTag( + authTag, authTagStart, (short) authTagLen, (short) AES_GCM_TAG_LENGTH); + return verification; + } + + public HMACKey cmacKdf( + KMKey preSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength) { + // Note: the variables i and L correspond to i and L in the standard. See page 12 of + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf. + try { + // This is hardcoded to requirement - 32 byte output with two concatenated + // 16 bytes K1 and K2. + final byte n = 2; // hardcoded + + // [i] counter - 32 bits + short iBufLen = 4; + short keyOutLen = n * 16; + // Convert Hmackey to AES Key as the algorithm is ALG_AES_CMAC_128. + KMHmacKey hmacKey = ((KMHmacKey) preSharedKey); + hmacKey.hmacKey.getKey(tmpArray, (short) 0); + aesKeys[KEYSIZE_256_OFFSET].setKey(tmpArray, (short) 0); + // Initialize the key derivation function. + kdf.init(aesKeys[KEYSIZE_256_OFFSET], Signature.MODE_SIGN); + // Clear the tmpArray buffer. + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + + Util.arrayFillNonAtomic(tmpArray, (short) 0, iBufLen, (byte) 0); + Util.arrayFillNonAtomic(tmpArray, (short) iBufLen, keyOutLen, (byte) 0); + + byte i = 1; + short pos = 0; + while (i <= n) { + tmpArray[3] = i; + // 4 bytes of iBuf with counter in it + kdf.update(tmpArray, (short) 0, (short) iBufLen); + kdf.update(label, labelStart, (short) labelLen); // label + kdf.update( + CMAC_KDF_CONSTANT_ZERO, + (short) 0, + (short) CMAC_KDF_CONSTANT_ZERO.length); // 1 byte of 0x00 + kdf.update(context, contextStart, contextLength); // context + // 4 bytes of L - signature of 16 bytes + pos = + kdf.sign( + CMAC_KDF_CONSTANT_L, + (short) 0, + (short) CMAC_KDF_CONSTANT_L.length, + tmpArray, + (short) (iBufLen + pos)); + i++; + } + return createHMACKey(tmpArray, (short) iBufLen, (short) keyOutLen); + } finally { + clean(); + } + } + + public short hmacSign( + HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + hmacSignature.init(key, Signature.MODE_SIGN); + return hmacSignature.sign(data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart) { + HMACKey key = createHMACKey(keyBuf, keyStart, keyLength); + return hmacSign(key, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + Object key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + if (!(key instanceof KMHmacKey)) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + KMHmacKey hmacKey = (KMHmacKey) key; + return hmacSign(hmacKey.hmacKey, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + try { + KMAESKey aesKey = (KMAESKey) masterkey; + short keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + return hmacSign( + tmpArray, (short) 0, keyLen, data, dataStart, dataLength, signature, signatureStart); + } finally { + clean(); + } + } + + @Override + public boolean hmacVerify( + KMKey key, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart, + short macLength) { + KMHmacKey hmacKey = (KMHmacKey) key; + hmacSignature.init(hmacKey.hmacKey, Signature.MODE_VERIFY); + return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength); + } + + @Override + public short rsaDecipherOAEP256( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + key.setExponent(secret, (short) secretStart, (short) secretLength); + key.setModulus(modBuffer, (short) modOff, (short) modLength); + rsaOaepDecipher.init(key, Cipher.MODE_DECRYPT); + return rsaOaepDecipher.doFinal( + inputDataBuf, + (short) inputDataStart, + (short) inputDataLength, + outputDataBuf, + (short) outputDataStart); + } + + private byte mapSignature256Alg(byte alg, byte padding, byte digest) { + switch (alg) { + case KMType.RSA: + switch (padding) { + case KMType.RSA_PKCS1_1_5_SIGN: + { + if (digest == KMType.DIGEST_NONE) { + return KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST; + } else { + return Signature.ALG_RSA_SHA_256_PKCS1; + } + } + case KMType.RSA_PSS: + return Signature.ALG_RSA_SHA_256_PKCS1_PSS; + case KMType.PADDING_NONE: + return KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD; + } + break; + case KMType.EC: + if (digest == KMType.DIGEST_NONE) { + return KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST; + } else { + return Signature.ALG_ECDSA_SHA_256; + } + case KMType.HMAC: + return Signature.ALG_HMAC_SHA_256; + } + return -1; + } + + private byte mapCipherAlg(byte alg, byte padding, byte blockmode, byte digest) { + switch (alg) { + case KMType.AES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; + case KMType.CTR: + return Cipher.ALG_AES_CTR; + case KMType.GCM: + return AEADCipher.ALG_AES_GCM; + } + break; + case KMType.DES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_DES_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_DES_CBC_NOPAD; + } + break; + case KMType.RSA: + switch (padding) { + case KMType.PADDING_NONE: + return Cipher.ALG_RSA_NOPAD; + case KMType.RSA_PKCS1_1_5_ENCRYPT: + return Cipher.ALG_RSA_PKCS1; + case KMType.RSA_OAEP: + { + if (digest == KMType.SHA1) { + /* MGF Digest is SHA1 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1; + } else if (digest == KMType.SHA2_256) { + /* MGF Digest is SHA256 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256; + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + break; + } + return -1; + } + + public KMOperation createSymmetricCipher( + short alg, + short purpose, + short macLength, + short blockMode, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] ivBuffer, + short ivStart, + short ivLength, + boolean isRkp) { + + short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode, (byte) 0); + KMOperation operation = null; + if (isRkp) { + operation = poolMgr.getRKpOperation(purpose, cipherAlg, alg, padding, blockMode, macLength); + } else { + operation = + poolMgr.getOperationImpl( + purpose, cipherAlg, alg, padding, blockMode, macLength, secretLength, false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + Key key = (Key) keyObj.keyObjectInst; + switch (secretLength) { + case 32: + case 16: + ((AESKey) key).setKey(secret, secretStart); + break; + case 24: + ((DESKey) key).setKey(secret, secretStart); + break; + default: + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + break; + } + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, ivBuffer, ivStart, ivLength); + return operation; + } + + public KMOperation createHmacSignerVerifier( + short purpose, + short digest, + byte[] secret, + short secretStart, + short secretLength, + boolean isRkp) { + KMOperation operation = null; + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (isRkp) { + operation = + poolMgr.getRKpOperation( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + key.setKey(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + private KMOperation createHmacSignerVerifier( + short purpose, short digest, HMACKey hmacKey, boolean isTrustedConf) { + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KMOperation operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + isTrustedConf); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + short len = hmacKey.getKey(tmpArray, (short) 0); + key.setKey(tmpArray, (short) 0, len); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + KMKey keyPair, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.EC: + // get EC private key buffer + ECPrivateKey ecPrivKey = + (ECPrivateKey) ((KMECDeviceUniqueKeyPair) keyPair).ecKeyPair.getPrivate(); + short ecPrivKeyLen = ecPrivKey.getS(tmpArray, (short) 0); + opr = createEcSigner(digest, tmpArray, (short) 0, ecPrivKeyLen, true /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + case KMType.DES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + false /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, false /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot) { + short keyLen = 0; + globalOperation.setPurpose(purpose); + globalOperation.setAlgorithmType(alg); + globalOperation.setPaddingAlgorithm(padding); + globalOperation.setBlockMode(blockMode); + try { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey aesKey = (KMAESKey) key; + keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + switch (alg) { + case KMType.HMAC: + HMACKey hmackey = createHMACKey(tmpArray, (short) 0, keyLen); + globalOperation.setSignature(hmacSignature); + globalOperation.init(hmackey, digest, null, (short) 0, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } finally { + clean(); + } + return globalOperation; + } + + @Override + public KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey) { + KMHmacKey key = (KMHmacKey) computedHmacKey; + return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true); + } + + public KMOperation createRsaSigner( + short digest, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte alg = mapSignature256Alg(KMType.RSA, (byte) padding, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createRsaDecipher( + short padding, + short mgfDigest, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0, (byte) mgfDigest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.DECRYPT, + cipherAlg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createEcSigner( + short digest, byte[] secret, short secretStart, short secretLength, boolean isRkp) { + KMOperation operation = null; + byte alg = mapSignature256Alg(KMType.EC, (byte) 0, (byte) digest); + if (isRkp) { + operation = + poolMgr.getRKpOperation( + KMType.SIGN, + Signature.ALG_ECDSA_SHA_256, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + } + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createKeyAgreement(byte[] secret, short secretStart, short secretLength) { + KMOperation operation = + poolMgr.getOperationImpl( + KMType.AGREE_KEY, + KeyAgreement.ALG_EC_SVDP_DH_PLAIN, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + KMOperation opr = null; + if (alg == KMType.RSA) { + switch (purpose) { + case KMType.SIGN: + opr = + createRsaSigner( + digest, + padding, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + case KMType.DECRYPT: + opr = + createRsaDecipher( + padding, + mgfDigest, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else if (alg == KMType.EC) { + switch (purpose) { + case KMType.SIGN: + opr = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength, false); + break; + + case KMType.AGREE_KEY: + opr = createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return opr; + } + + @Override + public short cmacKDF( + KMKey pSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] keyBuf, + short keyStart) { + HMACKey key = + cmacKdf(pSharedKey, label, labelStart, labelLen, context, contextStart, contextLength); + return key.getKey(keyBuf, keyStart); + } + + @Override + public boolean isUpgrading() { + return UpgradeManager.isUpgrading(); + } + + @Override + public KMKey createMasterKey(KMKey masterKey, short keySizeBits) { + try { + if (masterKey == null) { + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false); + masterKey = new KMAESKey(key); + } + short keyLen = (short) (keySizeBits / 8); + Util.arrayFillNonAtomic(tmpArray, (short) 0, keyLen, (byte) 0); + getTrueRandomNumber(tmpArray, (short) 0, keyLen); + ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); + return (KMKey) masterKey; + } finally { + clean(); + } + } + + @Override + public KMKey createPreSharedKey(KMKey preSharedKey, byte[] keyData, short offset, short length) { + short lengthInBits = (short) (length * 8); + if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (preSharedKey == null) { + HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, lengthInBits, false); + preSharedKey = new KMHmacKey(key); + } + ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) preSharedKey; + } + + @Override + public KMKey createComputedHmacKey( + KMKey computedHmacKey, byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (computedHmacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + computedHmacKey = new KMHmacKey(key); + } + ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) computedHmacKey; + } + + @Override + public short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + ECPrivateKey key = (ECPrivateKey) ecKeyPair.getPrivate(); + key.setS(secret, secretStart, secretLength); + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_RSA, Cipher.PAD_PKCS1); + + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + ; + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuf, modStart, modLength); + + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public boolean isAttestationKeyProvisioned() { + return false; + } + + @Override + public short getAttestationKeyAlgorithm() { + return KMType.INVALID_VALUE; + } + + @Override + public short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // HMAC_extract + hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, tmpArray, (short) 0); + // HMAC_expand + return hkdfExpand(tmpArray, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen); + } + + private short hkdfExtract( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] out, + short off) { + // https://tools.ietf.org/html/rfc5869#section-2.2 + HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen); + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off); + } + + private short hkdfExpand( + byte[] prk, + short prkOff, + short prkLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // https://tools.ietf.org/html/rfc5869#section-2.3 + short digestLen = (short) 32; // SHA256 digest length. + // Calculate no of iterations N. + short n = (short) ((short) (outLen + digestLen - 1) / digestLen); + if (n > 255) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); + Util.arrayFill(tmpArray, (short) 0, (short) 33, (byte) 0); + short bytesCopied = 0; + short len = 0; + for (short i = 0; i < n; i++) { + tmpArray[0]++; + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + if (i != 0) { + hmacSignature.update(tmpArray, (short) 1, (short) 32); + } + hmacSignature.update(info, infoOff, infoLen); + len = hmacSignature.sign(tmpArray, (short) 0, (short) 1, tmpArray, (short) 1); + if ((short) (bytesCopied + len) > outLen) { + len = (short) (outLen - bytesCopied); + } + Util.arrayCopyNonAtomic(tmpArray, (short) 1, out, (short) (outOff + bytesCopied), len); + bytesCopied += len; + } + return outLen; + } + + @Override + public short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff) { + keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen)); + return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff); + } + + @Override + public boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic(); + key.setW(pubKey, pubKeyOffset, pubKeyLen); + signer.init(key, Signature.MODE_VERIFY); + return signer.verify( + inputDataBuf, + inputDataStart, + inputDataLength, + signatureDataBuf, + signatureDataStart, + (short) (signatureDataBuf[(short) (signatureDataStart + 1)] + 2)); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short signWithDeviceUniqueKey( + KMKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init( + ((KMECDeviceUniqueKeyPair) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (key == null) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + poolMgr.initECKey(ecKeyPair); + key = new KMECDeviceUniqueKeyPair(ecKeyPair); + } + ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPublic(); + ecKeyPair.setS(privKey, privKeyOff, privKeyLen); + ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen); + return (KMKey) key; + } + + @Override + public KMKey createRkpMacKey(KMKey rkpMacKey, byte[] keyData, short offset, short length) { + if (rkpMacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + rkpMacKey = new KMHmacKey(key); + } + ((KMHmacKey) rkpMacKey).hmacKey.setKey(keyData, offset, length); + return rkpMacKey; + } + + @Override + public short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) { + MessageDigest.OneShot mDigest = null; + short len = 0; + try { + mDigest = MessageDigest.OneShot.open(MessageDigest.ALG_SHA_256); + len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + } finally { + if (mDigest != null) { + mDigest.close(); + mDigest = null; + } + } + return len; + } + + public boolean isPowerReset() { + boolean flag = false; + if (resetFlag[0] == POWER_RESET_TRUE) { + resetFlag[0] = POWER_RESET_FALSE; + flag = true; + if (poolMgr != null) { + poolMgr.powerReset(); + } + } + return flag; + } + + @Override + public void onSave(Element element, byte interfaceType, Object object) { + element.write(interfaceType); + if (object == null) { + element.write(null); + return; + } + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey.onSave(element, (KMAESKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + KMECDeviceUniqueKeyPair.onSave(element, (KMECDeviceUniqueKeyPair) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + @Override + public Object onRestore(Element element) { + if (element == null) { + return null; + } + byte interfaceType = element.readByte(); + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_COMPUTED_HMAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.onRestore((AESKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.onRestore((KeyPair) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return null; + } + + @Override + public short getBackupPrimitiveByteCount(byte interfaceType) { + short primitiveCount = 1; // interface type + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + primitiveCount += KMAESKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + primitiveCount += KMECDeviceUniqueKeyPair.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return primitiveCount; + } + + @Override + public short getBackupObjectCount(byte interfaceType) { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.getBackupObjectCount(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + @Override + public boolean isBootSignalEventSupported() { + return false; + } + + @Override + public boolean isDeviceRebooted() { + return false; + } + + @Override + public void clearDeviceBooted(boolean resetBootFlag) { + // To be filled + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java new file mode 100644 index 0000000..05801b0 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java @@ -0,0 +1,198 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * The KMAttestationCert interface represents a X509 compliant attestation certificate required to + * support keymaster's attestKey function. This cert will be created according to the specifications + * given in android keymaster hal documentation. KMSeProvider has to provide the instance of this + * certificate. This interface is designed based on builder pattern and hence each method returns + * instance of cert. + */ +public interface KMAttestationCert { + + /** + * Set verified boot hash. + * + * @param obj This is a KMByteBlob containing hash + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootHash(short obj); + + /** + * Set verified boot key received during booting up. + * + * @param obj This is a KMByteBlob containing verified boot key. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootKey(short obj); + + /** + * Set verified boot state received during booting up. + * + * @param val This is a byte containing verified boot state value. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootState(byte val); + + /** + * Set uniqueId received from CA certificate during provisioning. + * + * @param scratchpad Buffer to store intermediate results. + * @param scratchPadOff Start offset of the scratchpad buffer. + * @param creationTime This buffer contains the CREATION_TIME value. + * @param creationTimeOff Start offset of creattionTime buffer. + * @param creationTimeLen Length of the creationTime buffer. + * @param attestAppId This buffer contains the ATTESTATION_APPLICATION_ID value. + * @param attestAppIdOff Start offset of the attestAppId buffer. + * @param attestAppIdLen Length of the attestAppId buffer. + * @param resetSinceIdRotation This holds the information of RESET_SINCE_ID_ROTATION. + * @param masterKey + * @return instance of KMAttestationCert. + */ + KMAttestationCert makeUniqueId( + byte[] scratchpad, + short scratchPadOff, + byte[] creationTime, + short creationTimeOff, + short creationTimeLen, + byte[] attestAppId, + short attestAppIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey); + + /** + * Set start time received from creation/activation time tag. Used for certificate's valid period. + * + * @param obj This is a KMByteBlob object containing start time. + * @param scratchpad Buffer to store intermediate results. + * @return instance of KMAttestationCert. + */ + KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad); + + /** + * Set expiry time received from expiry time tag or ca certificates expiry time. Used for + * certificate's valid period. + * + * @param usageExpiryTimeObj This is a KMByteBlob containing expiry time. certificate. + * @param scratchPad Buffer to store intermediate results. + * @return instance of KMAttestationCert + */ + KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad); + + /** + * Set device lock status received during booting time or due to device lock command. + * + * @param val This is true if device is locked. + * @return instance of KMAttestationCert + */ + KMAttestationCert deviceLocked(boolean val); + + /** + * Set public key to be attested received from attestKey command. + * + * @param obj This is KMByteBlob containing the public key. + * @return instance of KMAttestationCert + */ + KMAttestationCert publicKey(short obj); + + /** + * Set attestation challenge received from attestKey command. + * + * @param obj This is KMByteBlob containing the attestation challenge. + * @return instance of KMAttestationCert + */ + KMAttestationCert attestationChallenge(short obj); + + /** + * Set extension tag received from key characteristics which needs to be added to android + * extension. This method will called once for each tag. + * + * @param tag is the KMByteBlob containing KMTag. + * @param hwEnforced is true if the tag has to be added to hw enforced list or else added to sw + * enforced list. + * @return instance of KMAttestationCert + */ + KMAttestationCert extensionTag(short tag, boolean hwEnforced); + + /** + * Set ASN.1 encoded X509 issuer field received from attestation key CA cert. + * + * @param obj This is KMByteBlob containing the issuer. + * @return instance of KMAttestationCert + */ + KMAttestationCert issuer(short obj); + + /** + * Set byte buffer to be used to generate certificate. + * + * @param buf This is byte[] buffer. + * @param bufStart This is short start offset. + * @param maxLen This is short length of the buffer. + * @return instance of KMAttestationCert + */ + KMAttestationCert buffer(byte[] buf, short bufStart, short maxLen); + + /** + * Get the start of the certificate + * + * @return start of the attestation cert. + */ + short getCertStart(); + + /** + * Get the length of the certificate + * + * @return length of the attestation cert. + */ + short getCertLength(); + + /** + * Build a fake signed certificate. After this method executes the certificate is ready with the + * signature equal to 1 byte which is 0 and with rsa signature algorithm. + */ + void build(); + + /** + * Set the Serial number in the certificate. If no serial number is set then serial number is 1. + * + * @param serialNumber + */ + boolean serialNumber(short serialNumber); + + /** + * Set the Subject Name in the certificate. + * + * @param subject + */ + boolean subjectName(short subject); + + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert ecAttestKey(short attestKey, byte mode); + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java new file mode 100644 index 0000000..61ddb36 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java @@ -0,0 +1,17 @@ +package com.android.javacard.seprovider; + +/** + * This class holds different interface type constants to differentiate between the instances of + * Computed Hmac key, device unique key pair, RKP Mac key, and master key when passed as generic + * objects. These constants are used in upgrade flow to retrieve the size of the object and + * primitive types saved and restored for respective key types. + */ +public class KMDataStoreConstants { + // INTERFACE Types + public static final byte INTERFACE_TYPE_COMPUTED_HMAC_KEY = 0x01; + // 0x02 reserved for INTERFACE_TYPE_ATTESTATION_KEY + public static final byte INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR = 0x03; + public static final byte INTERFACE_TYPE_MASTER_KEY = 0x04; + public static final byte INTERFACE_TYPE_PRE_SHARED_KEY = 0x05; + public static final byte INTERFACE_TYPE_RKP_MAC_KEY = 0x06; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java new file mode 100644 index 0000000..0e430a3 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java @@ -0,0 +1,55 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for KeyPair. */ +public class KMECDeviceUniqueKeyPair implements KMKey { + + public KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKeyPair(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECDeviceUniqueKeyPair kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECDeviceUniqueKeyPair onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECDeviceUniqueKeyPair(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java new file mode 100644 index 0000000..83774ab --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java @@ -0,0 +1,138 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** + * This class provides support for ECDSA_NO_DIGEST signature algorithm. Added this because javacard + * 3.0.5 does not support this + */ +public class KMEcdsa256NoDigestSignature extends Signature { + + public static final byte ALG_ECDSA_NODIGEST = (byte) 0x67; + public static final byte MAX_NO_DIGEST_MSG_LEN = 32; + private byte algorithm; + private Signature inst; + + public KMEcdsa256NoDigestSignature(byte alg) { + algorithm = alg; + // There is no constant for no digest so ALG_ECDSA_SHA_256 is used. However, + // signPreComputedHash is used for signing which is equivalent to no digest sign. + inst = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return inst.getLength(); + } + + @Override + public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + try { + if (i1 > MAX_NO_DIGEST_MSG_LEN) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + // add zeros to the left + if (i1 < MAX_NO_DIGEST_MSG_LEN) { + Util.arrayFillNonAtomic( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + (byte) 0); + } + Util.arrayCopyNonAtomic( + bytes, + i, + KMAndroidSEProvider.getInstance().tmpArray, + (short) (MAX_NO_DIGEST_MSG_LEN - i1), + i1); + return inst.signPreComputedHash( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + bytes1, + i2); + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return inst.sign(bytes, i, i1, bytes1, i2); + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java new file mode 100644 index 0000000..69cb069 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java @@ -0,0 +1,32 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INVALID_INPUT_LENGTH = 21; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_ARGUMENT = 38; + public static final short UNKNOWN_ERROR = 1000; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java new file mode 100644 index 0000000..79983a2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; + +/** + * KMException is shared instance of exception used for all exceptions in the applet. It is used to + * throw EMError errors. + */ +public class KMException extends RuntimeException { + + private static short[] reason; + private static KMException exception; + + private KMException() {} + + public static short reason() { + return reason[0]; + } + + public static void throwIt(short e) { + if (reason == null) { + reason = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); + } + if (exception == null) { + exception = new KMException(); + } + reason[0] = e; + throw exception; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java new file mode 100644 index 0000000..e938a2b --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java @@ -0,0 +1,53 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.HMACKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for HMACKey. */ +public class KMHmacKey implements KMKey { + + public HMACKey hmacKey; + + public KMHmacKey(HMACKey key) { + hmacKey = key; + } + + public static void onSave(Element element, KMHmacKey kmKey) { + element.write(kmKey.hmacKey); + } + + public static KMHmacKey onRestore(HMACKey hmacKey) { + if (hmacKey == null) { + return null; + } + return new KMHmacKey(hmacKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java new file mode 100644 index 0000000..9894382 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This interface helps to decouple Javacard internal key objects from the keymaster package. Using + * Javacard key objects provides security by providing protection against side channel attacks. + * KMAESKey, KMECDeviceUniqueKey and KMHmacKey implements this interface. + */ +public interface KMKey { + short getPublicKey(byte[] buf, short offset); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java new file mode 100644 index 0000000..a37da08 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This class holds the KeyObject and its associated algorithm value. Each KMKeyObject is tied to + * one of the crypto operations. + */ +public class KMKeyObject { + public byte algorithm; + public Object keyObjectInst; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java new file mode 100644 index 0000000..12e691e --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java @@ -0,0 +1,75 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMOperation represents a persistent operation started by keymaster hal's beginOperation function. + * This operation is persistent i.e. it will be stored in non volatile memory of se card. It will be + * returned back to KMSEProvider for the reuse when the operation is finished. + */ +public interface KMOperation { + + // Used for cipher operations + short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for signature operations + short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength); + + // Used for finishing cipher operations or ecdh keyAgreement. + short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for finishing signing operations. + short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart); + + // Used for finishing verifying operations. + boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength); + + // Used for aborting the ongoing operations. + void abort(); + + // Used for AES GCM cipher operation. + void updateAAD(byte[] dataBuf, short dataStart, short dataLength); + + // Used for getting output size before finishing a AES GCM cipher operation. For encryption this + // will + // include the auth tag which is appended at the end of the encrypted data. For decryption this + // will be + // size of the decrypted data only. + short getAESGCMOutputSize(short dataSize, short macLength); + + KMKeyObject getKeyObject(); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java new file mode 100644 index 0000000..8059e44 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -0,0 +1,415 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.PrivateKey; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class contains the actual implementation of all the crypto operations. It internally uses + * the Javacard crypto library to perform the operations. + */ +public class KMOperationImpl implements KMOperation { + + private static final byte ALG_TYPE_OFFSET = 0x00; + private static final byte PADDING_OFFSET = 0x01; + private static final byte PURPOSE_OFFSET = 0x02; + private static final byte BLOCK_MODE_OFFSET = 0x03; + private static final byte MAC_LENGTH_OFFSET = 0x04; + private final byte[] EMPTY = {}; + // This will hold the length of the buffer stored inside the + // Java Card after the GCM update operation. + private static final byte AES_GCM_UPDATE_LEN_OFFSET = 0x05; + private static final byte PARAMETERS_LENGTH = 6; + private short[] parameters; + // Either one of Cipher/Signature instance is stored. + private Object[] operationInst; + + public KMOperationImpl() { + parameters = JCSystem.makeTransientShortArray(PARAMETERS_LENGTH, JCSystem.CLEAR_ON_RESET); + operationInst = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public short getPurpose() { + return parameters[PURPOSE_OFFSET]; + } + + public void setPurpose(short mode) { + parameters[PURPOSE_OFFSET] = mode; + } + + public short getMacLength() { + return parameters[MAC_LENGTH_OFFSET]; + } + + public void setMacLength(short macLength) { + parameters[MAC_LENGTH_OFFSET] = macLength; + } + + public short getPaddingAlgorithm() { + return parameters[PADDING_OFFSET]; + } + + public void setPaddingAlgorithm(short alg) { + parameters[PADDING_OFFSET] = alg; + } + + public void setBlockMode(short mode) { + parameters[BLOCK_MODE_OFFSET] = mode; + } + + public short getBlockMode() { + return parameters[BLOCK_MODE_OFFSET]; + } + + public short getAlgorithmType() { + return parameters[ALG_TYPE_OFFSET]; + } + + public void setAlgorithmType(short cipherAlg) { + parameters[ALG_TYPE_OFFSET] = cipherAlg; + } + + public void setCipher(Cipher cipher) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = cipher; + } + + public void setSignature(Signature signer) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = signer; + } + + public void setKeyAgreement(KeyAgreement keyAgreement) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = keyAgreement; + } + + public boolean isResourceMatches(Object object, byte resourceType) { + return operationInst[resourceType] == object; + } + + public void setKeyObject(KMKeyObject keyObject) { + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = keyObject; + } + + public KMKeyObject getKeyObject() { + return (KMKeyObject) operationInst[KMPoolManager.RESOURCE_TYPE_KEY]; + } + + private void reset() { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = null; + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = null; + parameters[MAC_LENGTH_OFFSET] = KMType.INVALID_VALUE; + parameters[AES_GCM_UPDATE_LEN_OFFSET] = 0; + parameters[BLOCK_MODE_OFFSET] = KMType.INVALID_VALUE; + parameters[PURPOSE_OFFSET] = KMType.INVALID_VALUE; + parameters[ALG_TYPE_OFFSET] = KMType.INVALID_VALUE; + parameters[PADDING_OFFSET] = KMType.INVALID_VALUE; + } + + private byte mapPurpose(short purpose) { + switch (purpose) { + case KMType.ENCRYPT: + return Cipher.MODE_ENCRYPT; + case KMType.DECRYPT: + return Cipher.MODE_DECRYPT; + case KMType.SIGN: + return Signature.MODE_SIGN; + case KMType.VERIFY: + return Signature.MODE_VERIFY; + } + return -1; + } + + private void initSymmetricCipher(Key key, byte[] ivBuffer, short ivStart, short ivLength) { + Cipher symmCipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + byte cipherAlg = symmCipher.getAlgorithm(); + switch (cipherAlg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_CTR: + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + symmCipher.init(key, mapPurpose(getPurpose())); + break; + case Cipher.ALG_DES_CBC_NOPAD: + // Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + // While sending back the iv, send only 8 bytes. + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, (short) 8); + break; + case AEADCipher.ALG_AES_GCM: + ((AEADCipher) symmCipher).init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + private void initRsa(Key key, short digest) { + if (KMType.SIGN == getPurpose()) { + byte mode; + if (getPaddingAlgorithm() == KMType.PADDING_NONE + || (getPaddingAlgorithm() == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { + mode = Cipher.MODE_DECRYPT; + } else { + mode = Signature.MODE_SIGN; + } + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key, mode); + } else { // RSA Cipher + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + private void initEc(Key key) { + if (KMType.AGREE_KEY == getPurpose()) { + ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key); + } else { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + public void init(Key key, short digest, byte[] buf, short start, short length) { + switch (getAlgorithmType()) { + case KMType.AES: + case KMType.DES: + initSymmetricCipher(key, buf, start, length); + break; + case KMType.HMAC: + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init(key, mapPurpose(getPurpose())); + break; + case KMType.RSA: + initRsa(key, digest); + break; + case KMType.EC: + initEc(key); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + short len = + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (parameters[ALG_TYPE_OFFSET] == KMType.AES && parameters[BLOCK_MODE_OFFSET] == KMType.GCM) { + // Every time Block size data is stored as intermediate result. + parameters[AES_GCM_UPDATE_LEN_OFFSET] += (short) (inputDataLength - len); + } + return len; + } + + @Override + public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength); + return 0; + } + + private short finishKeyAgreement( + byte[] publicKey, short start, short len, byte[] output, short outputStart) { + return ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .generateSecret(publicKey, start, len, output, outputStart); + } + + private short finishCipher( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + short len = 0; + try { + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + Cipher cipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + short cipherAlg = parameters[ALG_TYPE_OFFSET]; + short blockMode = parameters[BLOCK_MODE_OFFSET]; + short mode = parameters[PURPOSE_OFFSET]; + short macLength = parameters[MAC_LENGTH_OFFSET]; + short padding = parameters[PADDING_OFFSET]; + + if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.DECRYPT) { + inputDataLen = (short) (inputDataLen - macLength); + } + } else if ((cipherAlg == KMType.DES || cipherAlg == KMType.AES) + && padding == KMType.PKCS7 + && mode == KMType.ENCRYPT) { + byte blkSize = 16; + byte paddingBytes; + short inputlen = inputDataLen; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + // padding bytes + if (inputlen % blkSize == 0) { + paddingBytes = blkSize; + } else { + paddingBytes = (byte) (blkSize - (inputlen % blkSize)); + } + // final len with padding + inputlen = (short) (inputlen + paddingBytes); + // intermediate buffer to copy input data+padding + // fill in the padding + Util.arrayFillNonAtomic(tmpArray, (short) 0, inputlen, paddingBytes); + // copy the input data + Util.arrayCopyNonAtomic(inputDataBuf, inputDataStart, tmpArray, (short) 0, inputDataLen); + inputDataBuf = tmpArray; + inputDataLen = inputlen; + inputDataStart = 0; + } + len = + cipher.doFinal( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + if ((cipherAlg == KMType.AES || cipherAlg == KMType.DES) + && padding == KMType.PKCS7 + && mode == KMType.DECRYPT) { + byte blkSize = 16; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + if (len > 0) { + // verify if padding is corrupted. + byte paddingByte = outputDataBuf[(short) (outputDataStart + len - 1)]; + // padding byte always should be <= block size + if ((short) paddingByte > blkSize || (short) paddingByte <= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + for (short j = 1; j <= paddingByte; ++j) { + if (outputDataBuf[(short) (outputDataStart + len - j)] != paddingByte) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + len = (short) (len - (short) paddingByte); // remove the padding bytes + } + } else if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.ENCRYPT) { + len += + ((AEADCipher) cipher) + .retrieveTag(outputDataBuf, (short) (outputDataStart + len), macLength); + } else { + boolean verified = + ((AEADCipher) cipher) + .verifyTag( + inputDataBuf, (short) (inputDataStart + inputDataLen), macLength, macLength); + if (!verified) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + } + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + return len; + } + + @Override + public short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + if (parameters[PURPOSE_OFFSET] == KMType.AGREE_KEY) { + return finishKeyAgreement( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } else { + return finishCipher( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } + } + + @Override + public short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); + } + + @Override + public boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); + } + + @Override + public void abort() { + // Few simulators does not reset the Hmac signer instance on init so as + // a workaround to reset the hmac signer instance in case of abort/failure of the operation + // the corresponding sign / verify function is called. + if (operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] != null) { + if ((parameters[PURPOSE_OFFSET] == KMType.SIGN || parameters[PURPOSE_OFFSET] == KMType.VERIFY) + && (((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).getAlgorithm() + == Signature.ALG_HMAC_SHA_256)) { + Signature signer = (Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + try { + if (parameters[PURPOSE_OFFSET] == KMType.SIGN) { + signer.sign(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0); + } else { + signer.verify(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0, (short) 0); + } + } catch (Exception e) { + // Ignore. + } + } + } + reset(); + } + + @Override + public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { + ((AEADCipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .updateAAD(dataBuf, dataStart, dataLength); + } + + @Override + public short getAESGCMOutputSize(short dataSize, short macLength) { + if (parameters[PURPOSE_OFFSET] == KMType.ENCRYPT) { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize + macLength); + } else { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize - macLength); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java new file mode 100644 index 0000000..9bc258c --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -0,0 +1,657 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class creates and manages all the cipher, signer, key agreement, operation and trusted + * confirmation pool instances. Each cipher or signer pool can hold a maximum of 4 instances per + * algorithm; however, only one instance of each algorithm is created initially and if required more + * instances are created dynamically. A maximum of four operations can be performed simultaneously. + * Upon reaching the maximum limit of 4, further operations or crypto instances will throw a + * TOO_MANY_OPERATIONS error. TrustedConfirmation pool is to support any operation which has the + * TRUSTED_CONFIRMATION tag in its key parameters. + */ +public class KMPoolManager { + + public static final byte MAX_OPERATION_INSTANCES = 4; + private static final byte HMAC_MAX_OPERATION_INSTANCES = 8; + public static final byte AES_128 = 0x04; + public static final byte AES_256 = 0x05; + // Resource type constants + public static final byte RESOURCE_TYPE_CRYPTO = 0x00; + public static final byte RESOURCE_TYPE_KEY = 0x01; + // static final variables + // -------------------------------------------------------------- + // P-256 Curve Parameters + static byte[] secp256r1_P; + static byte[] secp256r1_A; + + static byte[] secp256r1_B; + static byte[] secp256r1_S; + + // Uncompressed form + static byte[] secp256r1_UCG; + static byte[] secp256r1_N; + static final byte secp256r1_H = 1; + // -------------------------------------------------------------- + + // Cipher pool + private Object[] cipherPool; + // Signature pool + private Object[] signerPool; + // Keyagreement pool + private Object[] keyAgreementPool; + // KMOperationImpl pool + private Object[] operationPool; + // Hmac signer pool which is used to support TRUSTED_CONFIRMATION_REQUIRED tag. + private Object[] hmacSignOperationPool; + + private Object[] keysPool; + // RKP uses AESGCM and HMAC in generateCSR flow. + KMOperation rkpOPeration; + Signature rkpEc; + KMKeyObject rkpEcKey; + + final byte[] KEY_ALGS = { + AES_128, AES_256, KMType.DES, KMType.RSA, KMType.EC, KMType.HMAC, + }; + + final byte[] CIPHER_ALGS = { + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, + Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, + Cipher.ALG_DES_CBC_NOPAD, + Cipher.ALG_DES_ECB_NOPAD, + Cipher.ALG_AES_CTR, + Cipher.ALG_RSA_PKCS1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256, + Cipher.ALG_RSA_NOPAD, + AEADCipher.ALG_AES_GCM + }; + + final byte[] SIG_ALGS = { + Signature.ALG_RSA_SHA_256_PKCS1, + Signature.ALG_RSA_SHA_256_PKCS1_PSS, + Signature.ALG_ECDSA_SHA_256, + Signature.ALG_HMAC_SHA_256, + KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD, + KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST, + KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST + }; + + final byte[] KEY_AGREE_ALGS = {KeyAgreement.ALG_EC_SVDP_DH_PLAIN}; + + private static KMPoolManager poolManager; + + public static KMPoolManager getInstance() { + if (poolManager == null) { + poolManager = new KMPoolManager(); + } + return poolManager; + } + + public static void initStatics() { + secp256r1_P = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF + }; + + secp256r1_A = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFC + }; + + secp256r1_B = + new byte[] { + (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, + (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, + (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, + (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, + (byte) 0x60, (byte) 0x4B + }; + + secp256r1_S = + new byte[] { + (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, + (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, + (byte) 0x7E, (byte) 0x90 + }; + + // Uncompressed form + secp256r1_UCG = + new byte[] { + (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, + (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, + (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, + (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, + (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, + (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, + (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, + (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, + (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, + (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, + (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 + }; + + secp256r1_N = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, + (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, + (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, + (byte) 0x25, (byte) 0x51 + }; + } + + private KMPoolManager() { + initStatics(); + cipherPool = new Object[(short) (CIPHER_ALGS.length * MAX_OPERATION_INSTANCES)]; + // Extra 4 algorithms are used to support TRUSTED_CONFIRMATION_REQUIRED feature. + signerPool = + new Object[(short) ((SIG_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + keyAgreementPool = new Object[(short) (KEY_AGREE_ALGS.length * MAX_OPERATION_INSTANCES)]; + + keysPool = + new Object[(short) ((KEY_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + operationPool = new Object[MAX_OPERATION_INSTANCES]; + hmacSignOperationPool = new Object[MAX_OPERATION_INSTANCES]; + /* Initialize pools */ + initializeOperationPool(); + initializeHmacSignOperationPool(); + initializeSignerPool(); + initializeCipherPool(); + initializeKeyAgreementPool(); + initializeKeysPool(); + // Initialize the Crypto and Key objects required for RKP flow. + initializeRKpObjects(); + } + + private void initializeRKpObjects() { + rkpOPeration = new KMOperationImpl(); + rkpEc = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + rkpEcKey = createKeyObjectInstance(KMType.EC); + } + + private void initializeKeysPool() { + for (short index = 0; index < KEY_ALGS.length; index++) { + keysPool[index] = createKeyObjectInstance(KEY_ALGS[index]); + } + } + + private void initializeOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + operationPool[index] = new KMOperationImpl(); + } + } + + private void initializeHmacSignOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + hmacSignOperationPool[index] = new KMOperationImpl(); + } + } + + // Create a signature instance of each algorithm once. + private void initializeSignerPool() { + short index; + for (index = 0; index < SIG_ALGS.length; index++) { + signerPool[index] = getSignatureInstance(SIG_ALGS[index]); + } + + // Allocate extra 4 HMAC signer instances required for trusted confirmation + for (short len = (short) (index + 4); index < len; index++) { + signerPool[index] = getSignatureInstance(Signature.ALG_HMAC_SHA_256); + } + } + + // Create a cipher instance of each algorithm once. + private void initializeCipherPool() { + for (short index = 0; index < CIPHER_ALGS.length; index++) { + cipherPool[index] = getCipherInstance(CIPHER_ALGS[index]); + } + } + + private void initializeKeyAgreementPool() { + for (short index = 0; index < KEY_AGREE_ALGS.length; index++) { + keyAgreementPool[index] = getKeyAgreementInstance(KEY_AGREE_ALGS[index]); + } + } + + private Object[] getCryptoPoolInstance(short purpose) { + switch (purpose) { + case KMType.AGREE_KEY: + return keyAgreementPool; + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return cipherPool; + + case KMType.SIGN: + case KMType.VERIFY: + return signerPool; + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private Object createInstance(short purpose, short alg) { + switch (purpose) { + case KMType.AGREE_KEY: + return getKeyAgreementInstance((byte) alg); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return getCipherInstance((byte) alg); + + case KMType.SIGN: + case KMType.VERIFY: + return getSignatureInstance((byte) alg); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private KeyAgreement getKeyAgreementInstance(byte alg) { + return KeyAgreement.getInstance(alg, false); + } + + private Signature getSignatureInstance(byte alg) { + if (KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD == alg + || KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST == alg) { + return new KMRsa2048NoDigestSignature(alg); + } else if (KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST == alg) { + return new KMEcdsa256NoDigestSignature(alg); + } else { + return Signature.getInstance(alg, false); + } + } + + private KMKeyObject createKeyObjectInstance(byte alg) { + Object keyObject = null; + switch (alg) { + case AES_128: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + break; + case AES_256: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + break; + case KMType.DES: + keyObject = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + break; + case KMType.RSA: + keyObject = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + break; + case KMType.EC: + keyObject = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + initECKey((KeyPair) keyObject); + break; + case KMType.HMAC: + keyObject = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + KMKeyObject ptr = new KMKeyObject(); + ptr.algorithm = alg; + ptr.keyObjectInst = keyObject; + return ptr; + } + + private Cipher getCipherInstance(byte alg) { + if ((KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 == alg) + || (KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 == alg)) { + return new KMRsaOAEPEncoding(alg); + } else { + return Cipher.getInstance(alg, false); + } + } + + /** + * Returns the first available resource from operation pool. + * + * @return instance of the available resource or null if no resource is available. + */ + public KMOperation getResourceFromOperationPool(boolean isTrustedConfOpr) { + short index = 0; + KMOperationImpl impl; + Object[] oprPool; + if (isTrustedConfOpr) { + oprPool = hmacSignOperationPool; + } else { + oprPool = operationPool; + } + while (index < oprPool.length) { + impl = (KMOperationImpl) oprPool[index]; + // Mode is always set. so compare using mode value. + if (impl.getPurpose() == KMType.INVALID_VALUE) { + return impl; + } + index++; + } + return null; + } + + private byte getAlgorithm(short purpose, Object object) { + switch (purpose) { + case KMType.AGREE_KEY: + return ((KeyAgreement) object).getAlgorithm(); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return ((Cipher) object).getAlgorithm(); + + case KMType.SIGN: + case KMType.VERIFY: + return ((Signature) object).getAlgorithm(); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return 0; + } + + private boolean isResourceBusy(Object obj, byte resourceType) { + short index = 0; + while (index < MAX_OPERATION_INSTANCES) { + if (((KMOperationImpl) operationPool[index]).isResourceMatches(obj, resourceType) + || ((KMOperationImpl) hmacSignOperationPool[index]) + .isResourceMatches(obj, resourceType)) { + return true; + } + index++; + } + return false; + } + + private void setObject(short purpose, KMOperation operation, Object obj) { + switch (purpose) { + case KMType.AGREE_KEY: + ((KMOperationImpl) operation).setKeyAgreement((KeyAgreement) obj); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + ((KMOperationImpl) operation).setCipher((Cipher) obj); + break; + case KMType.SIGN: + case KMType.VERIFY: + ((KMOperationImpl) operation).setSignature((Signature) obj); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } + + private void reserveOperation( + KMOperation operation, + short purpose, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + Object obj, + KMKeyObject keyObject) { + ((KMOperationImpl) operation).setPurpose(purpose); + ((KMOperationImpl) operation).setAlgorithmType(strongboxAlgType); + ((KMOperationImpl) operation).setPaddingAlgorithm(padding); + ((KMOperationImpl) operation).setBlockMode(blockMode); + ((KMOperationImpl) operation).setMacLength(macLength); + ((KMOperationImpl) operation).setKeyObject(keyObject); + setObject(purpose, operation, obj); + } + + public KMOperation getRKpOperation( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength) { + if (((KMOperationImpl) rkpOPeration).getPurpose() != KMType.INVALID_VALUE) { + // Should not come here. + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Object cryptoObj = null; + KMKeyObject keyObject = null; + + switch (alg) { + case Signature.ALG_ECDSA_SHA_256: + cryptoObj = rkpEc; + keyObject = rkpEcKey; + break; + default: + // Should not come here. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + reserveOperation( + rkpOPeration, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + cryptoObj, + keyObject); + return rkpOPeration; + } + + public KMOperation getOperationImpl( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + short secretLength, + boolean isTrustedConfOpr) { + KMOperation operation; + // Throw exception if no resource from operation pool is available. + if (null == (operation = getResourceFromOperationPool(isTrustedConfOpr))) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + // Get one of the pool instances (cipher / signer / keyAgreement) based on purpose. + Object[] pool = getCryptoPoolInstance(purpose); + short index = 0; + short usageCount = 0; + short maxOperations = MAX_OPERATION_INSTANCES; + if (Signature.ALG_HMAC_SHA_256 == alg) { + maxOperations = HMAC_MAX_OPERATION_INSTANCES; + } + + KMKeyObject keyObject = getKeyObjectFromPool(alg, secretLength, maxOperations); + while (index < pool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (pool[index] == null) { + // Create one of the instance (Cipher / Signer / KeyAgreement] based on purpose. + Object cipherObject = createInstance(purpose, alg); + JCSystem.beginTransaction(); + pool[index] = cipherObject; + JCSystem.commitTransaction(); + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + if (alg == getAlgorithm(purpose, pool[index])) { + // Check if the crypto instance is not busy and free to use. + if (!isResourceBusy(pool[index], RESOURCE_TYPE_CRYPTO)) { + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + usageCount++; + } + index++; + } + return operation; + } + + public KMKeyObject getKeyObjectFromPool(short alg, short secretLength, short maxOperations) { + KMKeyObject keyObject = null; + byte algo = mapAlgorithm(alg, secretLength); + short index = 0; + short usageCount = 0; + while (index < keysPool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (keysPool[index] == null) { + keyObject = createKeyObjectInstance(algo); + JCSystem.beginTransaction(); + keysPool[index] = keyObject; + JCSystem.commitTransaction(); + break; + } + keyObject = (KMKeyObject) keysPool[index]; + if (algo == keyObject.algorithm) { + // Check if the Object instance is not busy and free to use. + if (!isResourceBusy(keyObject, RESOURCE_TYPE_KEY)) { + break; + } + usageCount++; + } + index++; + } + return keyObject; + } + + private byte mapAlgorithm(short alg, short secretLength) { + byte algo = 0; + switch (alg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_AES_CTR: + case AEADCipher.ALG_AES_GCM: + if (secretLength == 16) { + algo = AES_128; + } else if (secretLength == 32) { + algo = AES_256; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + case Cipher.ALG_DES_CBC_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + algo = KMType.DES; + break; + case Cipher.ALG_RSA_PKCS1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + case Cipher.ALG_RSA_NOPAD: + case Signature.ALG_RSA_SHA_256_PKCS1: + case Signature.ALG_RSA_SHA_256_PKCS1_PSS: + case KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD: + case KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST: + algo = KMType.RSA; + break; + case Signature.ALG_ECDSA_SHA_256: + case KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST: + case KeyAgreement.ALG_EC_SVDP_DH_PLAIN: + algo = KMType.EC; + break; + case Signature.ALG_HMAC_SHA_256: + algo = KMType.HMAC; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return algo; + } + + public void initECKey(KeyPair ecKeyPair) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + ECPublicKey pubkey = (ECPublicKey) ecKeyPair.getPublic(); + pubkey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + pubkey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + pubkey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + pubkey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + pubkey.setK(secp256r1_H); + pubkey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + + privKey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + privKey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + privKey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + privKey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + privKey.setK(secp256r1_H); + privKey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + } + + public void powerReset() { + short index = 0; + while (index < operationPool.length) { + ((KMOperationImpl) operationPool[index]).abort(); + ((KMOperationImpl) hmacSignOperationPool[index]).abort(); + index++; + } + // release rkp operation + rkpOPeration.abort(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java new file mode 100644 index 0000000..4890ce2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -0,0 +1,140 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** This class provides support for RSA_NO_DIGEST signature algorithm. */ +public class KMRsa2048NoDigestSignature extends Signature { + + public static final byte ALG_RSA_SIGN_NOPAD = (byte) 0x65; + public static final byte ALG_RSA_PKCS1_NODIGEST = (byte) 0x66; + private byte algorithm; + private Cipher inst; + + public KMRsa2048NoDigestSignature(byte alg) { + algorithm = alg; + inst = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return algorithm; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] bytes, short i, short i1) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + padData(bytes, i, i1, KMAndroidSEProvider.getInstance().tmpArray, (short) 0); + return inst.doFinal( + KMAndroidSEProvider.getInstance().tmpArray, (short) 0, (short) 256, bytes1, i2); + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } + + private void padData(byte[] buf, short start, short len, byte[] outBuf, short outBufStart) { + if (!isValidData(buf, start, len)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(outBuf, (short) outBufStart, (short) 256, (byte) 0x00); + if (algorithm == ALG_RSA_SIGN_NOPAD) { // add zero to right + } else if (algorithm == ALG_RSA_PKCS1_NODIGEST) { // 0x00||0x01||PS||0x00 + outBuf[0] = 0x00; + outBuf[1] = 0x01; + Util.arrayFillNonAtomic(outBuf, (short) 2, (short) (256 - len - 3), (byte) 0xFF); + outBuf[(short) (256 - len - 1)] = 0x00; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + Util.arrayCopyNonAtomic(buf, start, outBuf, (short) (256 - len), len); + } + + private boolean isValidData(byte[] buf, short start, short len) { + if (algorithm == ALG_RSA_SIGN_NOPAD) { + if (len > 256) { + return false; + } + } else { // ALG_RSA_PKCS1_NODIGEST + if (len > 245) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + return false; + } + } + return true; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java new file mode 100644 index 0000000..bf34ea1 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java @@ -0,0 +1,289 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacardx.crypto.Cipher; + +/** This class has the implementation for RSA_OAEP decoding algorithm. */ +public class KMRsaOAEPEncoding extends Cipher { + + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 = (byte) 0x1E; + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 = (byte) 0x1F; + + final short MGF1_BUF_SIZE = 256; + static byte[] mgf1Buf; + private Cipher cipher; + private byte hash; + private byte mgf1Hash; + private byte algorithm; + + public KMRsaOAEPEncoding(byte alg) { + setDigests(alg); + cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + algorithm = alg; + if (null == mgf1Buf) { + mgf1Buf = + JCSystem.makeTransientByteArray(MGF1_BUF_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT); + } + } + + private void setDigests(byte alg) { + switch (alg) { + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA; + break; + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA_256; + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + } + + private short getDigestLength() { + switch (hash) { + case MessageDigest.ALG_SHA: + return MessageDigest.LENGTH_SHA; + case MessageDigest.ALG_SHA_224: + return MessageDigest.LENGTH_SHA_224; + case MessageDigest.ALG_SHA_256: + return MessageDigest.LENGTH_SHA_256; + case MessageDigest.ALG_SHA_384: + return MessageDigest.LENGTH_SHA_384; + case MessageDigest.ALG_SHA_512: + return MessageDigest.LENGTH_SHA_512; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return 0; + } + + @Override + public void init(Key theKey, byte theMode) throws CryptoException { + cipher.init(theKey, theMode); + } + + @Override + public void init(Key theKey, byte theMode, byte[] bArray, short bOff, short bLen) + throws CryptoException { + cipher.init(theKey, theMode, bArray, bOff, bLen); + } + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short doFinal( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + short len = cipher.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + + // https://tools.ietf.org/html/rfc8017#section-7.1 + // https://www.inf.pucrs.br/~calazans/graduate/TPVLSI_I/RSA-oaep_spec.pdf + // RSA OAEP Encoding and Decoding Mechanism for a 2048 bit RSA Key. + // Msg -> RSA-OAEP-ENCODE -> RSAEncryption -> RSADecryption -> + // RSA-OAEP-DECODE -> Msg + // RSA-OAEP-ENCODE generates an output length of 255, but RSAEncryption + // requires and input of length 256 so we pad 0 to the left of the input + // message and make the length equal to 256 and pass to RSAEncryption. + // RSADecryption takes input length equal to 256 and generates an + // output of length 256. After decryption the first byte of the output + // should be 0(left padding we did in encryption). + // RSA-OAEP-DECODE takes input of length 255 so remove the left padding of 1 + // byte. + if (len != 256 || outBuff[0] != 0) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayCopyNonAtomic( + outBuff, (short) (outOffset + 1), outBuff, (short) 0, (short) (len - 1)); + return rsaOAEPDecode(outBuff, (short) 0, (short) (len - 1)); + } + + @Override + public short update( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + return cipher.update(inBuff, inOffset, inLength, outBuff, outOffset); + } + + private void maskGenerationFunction1( + byte[] input, + short inputOffset, + short inputLen, + short expectedOutLen, + byte[] outBuf, + short outOffset) { + short counter = 0; + MessageDigest.OneShot md = null; + try { + md = MessageDigest.OneShot.open(mgf1Hash); + short digestLen = md.getLength(); + + Util.arrayCopyNonAtomic(input, inputOffset, mgf1Buf, (short) 0, inputLen); + while (counter < (short) (expectedOutLen / digestLen)) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + counter++; + } + + if ((short) (counter * digestLen) < expectedOutLen) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + } + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(mgf1Buf, (short) 0, (short) MGF1_BUF_SIZE, (byte) 0); + } + } + + // Integer to Octet String conversion. + private void I2OS(short i, byte[] out, short offset) { + Util.arrayFillNonAtomic(out, (short) offset, (short) 4, (byte) 0); + out[(short) (offset + 3)] = (byte) (i >>> 0); + out[(short) (offset + 2)] = (byte) (i >>> 8); + } + + private short rsaOAEPDecode(byte[] encodedMsg, short encodedMsgOff, short encodedMsgLen) { + MessageDigest.OneShot md = null; + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + + try { + short hLen = getDigestLength(); + + if (encodedMsgLen < (short) (2 * hLen + 1)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // encodedMsg will be in the format of maskedSeed||maskedDB. + // maskedSeed length is hLen and maskedDB length is (encodedMsgLen - hLen) + // Now retrieve the seedMask by calling MGF(maskedDB, hLen). The length + // of the seedMask is hLen. + // seedMask = MGF(maskedDB, hLen) + maskGenerationFunction1( + encodedMsg, + (short) (encodedMsgOff + hLen), + (short) (encodedMsgLen - hLen), + hLen, + tmpArray, + (short) 0); + + // Get the seed by doing XOR of (maskedSeed ^ seedMask). + // seed = (maskedSeed ^ seedMask) + for (short i = 0; i < hLen; i++) { + // Store the seed in encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i)] ^= tmpArray[i]; + } + + // Now get the dbMask by calling MGF(seed , (emLen-hLen)). + // dbMask = MGF(seed , (emLen-hLen)). + maskGenerationFunction1( + encodedMsg, + (short) encodedMsgOff, + hLen, + (short) (encodedMsgLen - hLen), + tmpArray, + (short) 0); + + // Get the DB value. DB = (maskedDB ^ dbMask) + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + for (short i = 0; i < (short) (encodedMsgLen - hLen); i++) { + // Store the DB inside encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i + hLen)] ^= tmpArray[i]; + } + + // Verify Hash. + md = MessageDigest.OneShot.open(hash); + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + md.doFinal(tmpArray, (short) 0, (short) 0, tmpArray, (short) 0); + if (0 + != Util.arrayCompare( + encodedMsg, (short) (encodedMsgOff + hLen), tmpArray, (short) 0, hLen)) { + // Verification failed. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + // Find the Message block in DB. + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + // The message will be located at the end of the Data block (DB). + // The DB block is first constructed by keeping the message at the end and + // to the message 0x01 byte is prepended. The hash of the + // encoding parameters is calculated and then copied from the + // starting of the block and a variable length of 0's are + // appended to the end of the hash till the 0x01 byte. + short start = (short) (encodedMsgOff + encodedMsgLen); + for (short i = (short) (encodedMsgOff + 2 * hLen); + i < (short) (encodedMsgOff + encodedMsgLen); + i++) { + if ((encodedMsg[i] != 0)) { + start = i; + break; + } + } + if ((start >= (short) (encodedMsgOff + encodedMsgLen)) || (encodedMsg[start] != 0x01)) { + // Bad Padding. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + start++; // Message starting pos. + if (start < (short) (encodedMsgOff + encodedMsgLen)) { + // Copy the message + Util.arrayCopyNonAtomic( + encodedMsg, + start, + encodedMsg, + encodedMsgOff, + (short) (encodedMsgLen - (start - encodedMsgOff))); + } + return (short) (encodedMsgLen - (start - encodedMsgOff)); + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(tmpArray, (short) 0, KMAndroidSEProvider.TMP_ARRAY_SIZE, (byte) 0); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java new file mode 100644 index 0000000..76f45e4 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java @@ -0,0 +1,780 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** + * KMSEProvider is facade to use SE specific methods. The main intention of this interface is to + * abstract the cipher, signature and backup and restore related functions. The instance of this + * interface is created by the singleton KMSEProviderImpl class for each provider. At a time there + * can be only one provider in the applet package. + */ +public interface KMSEProvider { + + /** + * This function tells if boot signal event is supported or not. + * + * @return true if supported, false otherwise. + */ + boolean isBootSignalEventSupported(); + + /** + * This function tells if the device is booted or not. + * + * @return true if device booted, false otherwise. + */ + boolean isDeviceRebooted(); + + /** + * This function is supposed to be used to reset the device booted stated after set boot param is + * handled + * + * @param resetBootFlag is false if event has been handled + */ + void clearDeviceBooted(boolean resetBootFlag); + + /** + * Create a symmetric key instance. If the algorithm and/or keysize are not supported then it + * should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer in which key has to be returned + * @param startOff is the start offset. + * @return length of the data in the buf. This should match the keysize (in bytes). + */ + short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff); + + /** + * Create a asymmetric key pair. If the algorithms are not supported then it should throw a + * CryptoException. For RSA the public key exponent must always be 0x010001. The key size of RSA + * key pair must be 2048 bits and key size of EC key pair must be for p256 curve. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer to return the private key exponent in case of RSA or private + * key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyMaxLength is the maximum length of this private key buffer. + * @param pubModBuf is the buffer to return the modulus in case of RSA or public key in case of + * EC. + * @param pubModStart is the start of offset. + * @param pubModMaxLength is the maximum length of this public key buffer. + * @param lengths is the actual length of the key pair - lengths[0] should be private key and + * lengths[1] should be public key. + */ + void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyMaxLength, + byte[] pubModBuf, + short pubModStart, + short pubModMaxLength, + short[] lengths); + + /** + * Initializes the trusted confirmation operation. + * + * @param computedHmacKey Instance of the computed Hmac key. + * @return instance of KMOperation. + */ + KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey); + + /** + * Verify that the imported key is valid. If the algorithm and/or keysize are not supported then + * it should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer that contains the symmetric key. + * @param startOff is the start offset. + * @param length of the data in the buf. This should match the keysize (in bytes). + * @return true if the symmetric key is supported and valid. + */ + boolean importSymmetricKey(byte alg, short keysize, byte[] buf, short startOff, short length); + + /** + * Validate that the imported asymmetric key pair is valid. For RSA the public key exponent must + * always be 0x010001. The key size of RSA key pair must be 2048 bits and key size of EC key pair + * must be for p256 curve. If the algorithms are not supported then it should throw a + * CryptoException. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer that contains the private key exponent in case of RSA or + * private key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyLength is the length of this private key buffer. + * @param pubModBuf is the buffer that contains the modulus in case of RSA or public key in case + * of EC. + * @param pubModStart is the start of offset. + * @param pubModLength is the length of this public key buffer. + * @return true if the key pair is supported and valid. + */ + boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This is a oneshot operation that generates random number of desired length. + * + * @param num is the buffer in which random number is returned to the applet. + * @param offset is start of the buffer. + * @param length indicates the size of buffer and desired length of random number in bytes. + */ + void newRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that adds the entropy to the entropy pool. This operation + * corresponds to addRndEntropy command. This method may ignore the added entropy value if the SE + * provider does not support it. + * + * @param num is the buffer in which entropy value is given. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void addRngEntropy(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that generates and returns back a true random number. + * + * @param num is the buffer in which entropy value is returned. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void getTrueRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that performs encryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported or if tag length is not equal to 16 or + * nonce length is not equal to 12. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param data is the buffer that contains data to encrypt. + * @param dataStart is the start of the data buffer. + * @param dataLen is the length of the data buffer. + * @param encData is the buffer of the output encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return length of the encrypted data. + */ + short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] data, + short dataStart, + short dataLen, + byte[] encData, + short encDataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs decryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param encData is the buffer of the input encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param encDataLen is the length of the data buffer. + * @param data is the buffer that contains output decrypted data. + * @param dataStart is the start of the data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return true if the authentication is valid. + */ + boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encData, + short encDataStart, + short encDataLen, + byte[] data, + short dataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs key derivation function using cmac kdf (CKDF) as + * defined in android keymaster hal definition. + * + * @param hmacKey of pre-shared key. + * @param label is the label to be used for ckdf. + * @param labelStart is the start of label. + * @param labelLen is the length of the label. + * @param context is the context to be used for ckdf. + * @param contextStart is the start of the context + * @param contextLength is the length of the context + * @param key is the output buffer to return the derived key + * @param keyStart is the start of the output buffer. + * @return length of the derived key buffer in bytes. + */ + short cmacKDF( + KMKey hmacKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] key, + short keyStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param hmacKey is the KMHmacKey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + Object hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. This is used to derive + * the key, which is used to encrypt the keyblob. + * + * @param masterkey of masterkey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that verifies the signature using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the signature buffer. + * @param signatureStart is the start of the signature buffer. + * @param signatureLen is the length of the signature buffer in bytes. + * @return true if the signature matches. + */ + boolean hmacVerify( + KMKey hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart, + short signatureLen); + + /** + * This is a oneshot operation that decrypts the data using RSA algorithm with oaep256 padding. + * The public exponent is always 0x010001. It throws CryptoException if OAEP encoding validation + * fails. + * + * @param privExp is the private exponent (2048 bit) buffer. + * @param privExpStart is the start of the private exponent buffer. + * @param privExpLength is the length of the private exponent buffer in bytes. + * @param modBuffer is the modulus (2048 bit) buffer. + * @param modOff is the start of the modulus buffer. + * @param modLength is the length of the modulus buffer in bytes. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the decrypted data. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short rsaDecipherOAEP256( + byte[] privExp, + short privExpStart, + short privExpLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 + * + * @param ikm is the buffer containing input key material. + * @param ikmOff is the start of the input key. + * @param ikmLen is the length of the input key. + * @param salt is the buffer containing the salt. + * @param saltOff is the start of the salt buffer. + * @param saltLen is the length of the salt buffer. + * @param info is the buffer containing the application specific information + * @param infoOff is the start of the info buffer. + * @param infoLen is the length of the info buffer. + * @param out is the output buffer. + * @param outOff is the start of the output buffer. + * @param outLen is the length of the expected out buffer. + * @return Length of the out buffer which is outLen. + */ + short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen); + + /** + * This function performs ECDH key agreement and generates a secret. + * + * @param privKey is the buffer containing the private key from first party. + * @param privKeyOff is the offset of the private key buffer. + * @param privKeyLen is the length of the private key buffer. + * @param publicKey is the buffer containing the public key from second party. + * @param publicKeyOff is the offset of the public key buffer. + * @param publicKeyLen is the length of the public key buffer. + * @param secret is the output buffer. + * @param secretOff is the offset of the output buffer. + * @return The length of the secret. + */ + short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff); + + /** + * This is a oneshort operation that verifies the data using EC public key + * + * @param pubKey is the public key buffer. + * @param pubKeyOffset is the start of the public key buffer. + * @param pubKeyLen is the length of the public key. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param signatureDataBuf is the buffer the signature input data. + * @param signatureDataStart is the start of the signature input data. + * @param signatureDataLen is the length of the signature input data. + * @return true if verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen); + + /** + * This is a oneshot operation that signs the data using device unique key. + * + * @param ecPrivKey instance of KMECDeviceUniqueKey to sign the input data. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short signWithDeviceUniqueKey( + KMKey deviceUniqueKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param key is a key object. + * @param interfaceType defines the type of key in the key object. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @param oneShot if true, creates oneshot operation. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot); + + /** + * This function creates an Operation instance only for RKP module. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + KMKey ecPrivKey, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using RSA + * and EC algorithms when keymaster hal's beginOperation function is executed. For RSA the public + * exponent is always 0x0100101. For EC the curve is always p256. The KMOperation instance can be + * reclaimed by the seProvider when KMOperation is finished or aborted. It throws CryptoException + * if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for RSA. It will be * KMType.SIGN and + * KMType.VERIFY for RSA and EC algorithms. + * @param alg is KMType.RSA or KMType.EC algorithms. + * @param padding is KMType.PADDING_NONE or KMType.RSA_OAEP, KMType.RSA_PKCS1_1_5_ENCRYPT, + * KMType.RSA_PKCS1_1_5_SIGN or KMType.RSA_PSS. + * @param digest is KMType.DIGEST_NONE or KMType.SHA2_256. + * @param mgfDigest is the MGF digest. + * @param privKeyBuf is the private key in case of EC or private key exponent is case of RSA. + * @param privKeyStart is the start of the private key. + * @param privKeyLength is the length of the private key. + * @param pubModBuf is the modulus (in case of RSA) or public key (in case of EC). + * @param pubModStart is the start of the modulus. + * @param pubModLength is the length of the modulus. + * @return KMOperation instance that can be executed. + */ + KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This function tells if applet is upgrading or not. + * + * @return true if upgrading, otherwise false. + */ + boolean isUpgrading(); + + /** + * This function generates an AES Key of keySizeBits, which is used as an master key. This + * generated key is maintained by the SEProvider. This function should be called only once at the + * time of installation. + * + * @param instance of the masterkey. + * @param keySizeBits key size in bits. + * @return An instance of KMMasterKey. + */ + KMKey createMasterKey(KMKey masterKey, short keySizeBits); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMComputedHmacKey. + */ + KMKey createComputedHmacKey(KMKey computedHmacKey, byte[] keyData, short offset, short length); + + /** Returns true if factory provisioned attestation key is supported. */ + boolean isAttestationKeyProvisioned(); + + /** + * Returns algorithm type of the attestation key. It can be KMType.EC or KMType.RSA if the + * attestation key is provisioned in the factory. + */ + short getAttestationKeyAlgorithm(); + + /** + * Creates an ECKey instance and sets the public and private keys to it. + * + * @param testMode to indicate if current execution is for test or production. + * @param pubKey buffer containing the public key. + * @param pubKeyOff public key buffer start offset. + * @param pubKeyLen public key buffer length. + * @param privKey buffer containing the private key. + * @param privKeyOff private key buffer start offset. + * @param privKeyLen private key buffer length. + * @return instance of KMDeviceUniqueKey. + */ + KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen); + + /** + * This is a one-shot operation the does digest of the input mesage. + * + * @param inBuff input buffer to be digested. + * @param inOffset start offset of the input buffer. + * @param inLength length of the input buffer. + * @param outBuff is the output buffer that contains the digested data. + * @param outOffset start offset of the digested output buffer. + * @return length of the digested data. + */ + short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset); + + /** + * This function generates a HMAC key from the provided key buffers. + * + * @param presharedKey instance of the presharedkey. + * @param key buffer containing the key data. + * @param offset start offset of the buffer. + * @param length is the length of the key. + * @return instance of KMPresharedKey. + */ + KMKey createPreSharedKey(KMKey presharedKey, byte[] key, short offset, short length); + + /** + * This function saves the key objects while upgrade. + * + * @param element instance of the Element class where the objects to be stored. + * @param interfaceType the type interface of the parent object. + * @param object instance of the object to be saved. + */ + void onSave(Element element, byte interfaceType, Object object); + + /** + * This function restores the the object from element instance. + * + * @param element instance of the Element class. + * @return restored object. + */ + Object onRestore(Element element); + + /** + * This function returns the count of the primitive bytes required to be stored by the + * implementation of the interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the primitive bytes. + */ + short getBackupPrimitiveByteCount(byte interfaceType); + + /** + * This function returns the object count required to be stored by the implementation of the + * interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the objects. + */ + short getBackupObjectCount(byte interfaceType); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMRkpMacKey. + */ + KMKey createRkpMacKey(KMKey createComputedHmacKey, byte[] keyData, short offset, short length); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java new file mode 100644 index 0000000..82cbe26 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java @@ -0,0 +1,79 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java new file mode 100644 index 0000000..420b2c7 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java @@ -0,0 +1,30 @@ +/* + * 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" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** This interface helps in storing and restoring the applet data during the applet upgrades. */ +public interface KMUpgradable { + + void onSave(Element ele); + + void onRestore(Element ele, short oldVersion, short currentVersion); + + short getBackupPrimitiveByteCount(); + + short getBackupObjectCount(); +} diff --git a/ready_se/google/keymint/KM300/Applet/README.md b/ready_se/google/keymint/KM300/Applet/README.md new file mode 100644 index 0000000..17e2e49 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/README.md @@ -0,0 +1,16 @@ +# JavaCardKeymaster Applet + +This directory contains the implementation of the Keymint 3.0 +interface, in the form of a JavaCard 3.0.5 applet which runs in a secure +element. It must be deployed in conjuction with the associated HAL, +which mediates between Android Keystore and this applet. + +# Supported Features! + + - Keymint 3.0 supported functions for required VTS compliance. + - SharedSecret 1.0 supported functions for required VTS compliance. + - RemotelyProvisionedComponent 3.0 supported functions for required VTS compliance. + +# Not supported features + - Factory provisioned attestation key will not be supported in this applet. + - Limited usage keys will not be supported in this applet. diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java new file mode 100644 index 0000000..aa54d54 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -0,0 +1,165 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMArray represents an array of KMTypes. Array is the sequence of elements of one or more sub + * types of KMType. It also acts as a vector of one subtype of KMTypes on the lines of class KMArray + * , where subType is subclass of KMType. Vector is the sequence of elements of one sub + * type e.g. KMType.BYTE_BLOB_TYPE. The KMArray instance maps to the CBOR type array. KMArray is a + * KMType and it further extends the value field in TLV_HEADER as ARRAY_HEADER struct{short subType; + * short length;} followed by sequence of short pointers to KMType instances. The subType can be 0 + * if this is an array or subType is short KMType value e.g. KMType.BYTE_BLOB_TYPE if this is a + * vector of that sub type. + */ +public class KMArray extends KMType { + + public static final short ANY_ARRAY_LENGTH = 0x1000; + private static final byte ARRAY_HEADER_SIZE = 4; + private static KMArray prototype; + + private KMArray() {} + + private static KMArray proto(short ptr) { + if (prototype == null) { + prototype = new KMArray(); + } + KMType.instanceTable[KM_ARRAY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short exp(short type) { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(ARRAY_TYPE, (short) (ARRAY_HEADER_SIZE + (length * 2))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMArray cast(short ptr) { + if (heap[ptr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short objPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (getStartOff() + (short) (index * 2)), objPtr); + } + + public short get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort(heap, (short) (getStartOff() + (short) (index * 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2)), + indexPtr1); + } + + public short containedType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); + } + + public short length() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short setLength(short len) { + return Util.setShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), len); + } + + public byte[] getBuffer() { + return heap; + } + + public void deleteLastEntry() { + short len = length(); + Util.setShort( + heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len - 1)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java new file mode 100644 index 0000000..22a16a3 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -0,0 +1,471 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This is a utility class that helps in parsing the PKCS8 encoded RSA and EC keys, certificate + * subject, subjectPublicKey info and ECDSA signatures. + */ +public class KMAsn1Parser { + + // Below are the ASN.1 tag types + public static final byte ASN1_OCTET_STRING = 0x04; + public static final byte ASN1_SEQUENCE = 0x30; + public static final byte ASN1_SET = 0x31; + public static final byte ASN1_INTEGER = 0x02; + public static final byte OBJECT_IDENTIFIER = 0x06; + public static final byte ASN1_A0_TAG = (byte) 0xA0; + public static final byte ASN1_A1_TAG = (byte) 0xA1; + public static final byte ASN1_BIT_STRING = 0x03; + + public static final byte ASN1_UTF8_STRING = 0x0C; + public static final byte ASN1_TELETEX_STRING = 0x14; + public static final byte ASN1_PRINTABLE_STRING = 0x13; + public static final byte ASN1_UNIVERSAL_STRING = 0x1C; + public static final byte ASN1_BMP_STRING = 0x1E; + public static final byte IA5_STRING = 0x16; + // OID of the EC P256 curve 1.2.840.10045.3.1.7 + public static final byte[] EC_CURVE = { + 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 + }; + // Constant for rsaEncryption pkcs#1 (1.2.840.113549.1.1.1) and NULL + public static final byte[] RSA_ALGORITHM = { + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 + }; + // Constant for ecPublicKey (1.2.840.10045.2.1) and prime256v1 (1.2.840.10045.3.1.7) + public static final byte[] EC_ALGORITHM = { + 0x06, + 0x07, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x02, + 0x01, + 0x06, + 0x08, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x03, + 0x01, + 0x07 + }; + // The maximum length of email id attribute. + public static final short MAX_EMAIL_ADD_LEN = 255; + // Datatable offsets. + private static final byte DATA_START_OFFSET = 0; + private static final byte DATA_LENGTH_OFFSET = 1; + private static final byte DATA_CURSOR_OFFSET = 2; + // This array contains the last byte of OID for each oid type. + // The first 4 bytes are common as shown above in COMMON_OID + private static final byte[] attributeOIds = { + 0x03, /* commonName COMMON_OID.3 */ 0x04, /* surName COMMON_OID.4*/ + 0x05, /* serialNumber COMMON_OID.5 */ 0x06, /* countryName COMMON_OID.6 */ + 0x07, /* locality COMMON_OID.7 */ 0x08, /* stateOrProviince COMMON_OID.8 */ + 0x0A, /* organizationName COMMON_OID.10 */ 0x0B, /* organizationalUnitName COMMON_OID.11 */ + 0x0C, /* title COMMON_OID.10 */ 0x29, /* name COMMON_OID.41 */ + 0x2A, /* givenName COMMON_OID.42 */ 0x2B, /* initials COMMON_OID.43 */ + 0x2C, /* generationQualifier COMMON_OID.44 */ 0x2E, /* dnQualifer COMMON_OID.46 */ + 0x41, /* pseudonym COMMON_OID.65 */ + }; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 124 + // TODO Specification does not mention about the DN_QUALIFIER_OID max length. + // So the max limit is set at 64. + // For name the RFC 5280 supports up to 32768, as Javacard doesn't support + // that much length, the max limit for name is set to 128. + private static final byte[] attributeValueMaxLen = { + 0x40, /* 1-64 commonName */ + 0x28, /* 1-40 surname */ + 0x40, /* 1-64 serial */ + 0x02, /* 1-2 country */ + (byte) 0x80, /* 1-128 locality */ + (byte) 0x80, /* 1-128 state */ + 0x40, /* 1-64 organization */ + 0x40, /* 1-64 organization unit*/ + 0x40, /* 1-64 title */ + 0x29, /* 1-128 name */ + 0x10, /* 1-16 givenName */ + 0x05, /* 1-5 initials */ + 0x03, /* 1-3 gen qualifier */ + 0x40, /* 1-64 dn-qualifier */ + (byte) 0x80 /* 1-128 pseudonym */ + }; + private static KMAsn1Parser inst; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21 + // 2.5.4 + public byte[] COMMON_OID = new byte[] {0x06, 0x03, 0x55, 0x04}; + public byte[] EMAIL_ADDRESS_OID = + new byte[] { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x09, 0x01 + }; + private byte[] data; + private short[] dataInfo; + + private KMAsn1Parser() { + dataInfo = JCSystem.makeTransientShortArray((short) 3, JCSystem.CLEAR_ON_RESET); + dataInfo[DATA_START_OFFSET] = 0; + dataInfo[DATA_LENGTH_OFFSET] = 0; + dataInfo[DATA_CURSOR_OFFSET] = 0; + } + + public static KMAsn1Parser instance() { + if (inst == null) { + inst = new KMAsn1Parser(); + } + return inst; + } + + public short decodeRsa(short blob) { + init(blob); + decodeCommon((short) 0, RSA_ALGORITHM); + return decodeRsaPrivateKey((short) 0); + } + + public short decodeEc(short blob) { + init(blob); + decodeCommon((short) 0, EC_ALGORITHM); + return decodeEcPrivateKey((short) 1); + } + + /* + Name ::= CHOICE { -- only one possibility for now -- + rdnSequence RDNSequence } + RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + RelativeDistinguishedName ::= + SET SIZE (1..MAX) OF AttributeTypeAndValue + AttributeTypeAndValue ::= SEQUENCE { + type AttributeType, + value AttributeValue } + AttributeType ::= OBJECT IDENTIFIER + AttributeValue ::= ANY -- DEFINED BY AttributeType + */ + public void validateDerSubject(short blob) { + init(blob); + header(ASN1_SEQUENCE); + while (dataInfo[DATA_CURSOR_OFFSET] + < ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + header(ASN1_SET); + header(ASN1_SEQUENCE); + // Parse and validate OBJECT-IDENTIFIER and Value fields + // Cursor is incremented in validateAttributeTypeAndValue. + validateAttributeTypeAndValue(); + } + } + + public short decodeEcSubjectPublicKeyInfo(short blob) { + init(blob); + header(ASN1_SEQUENCE); + short len = header(ASN1_SEQUENCE); + short ecPublicInfo = KMByteBlob.instance(len); + getBytes(ecPublicInfo); + if (Util.arrayCompare( + KMByteBlob.cast(ecPublicInfo).getBuffer(), + KMByteBlob.cast(ecPublicInfo).getStartOff(), + EC_ALGORITHM, + (short) 0, + KMByteBlob.cast(ecPublicInfo).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + return pubKey; + } + + // Seq[Int,Int,Int,Int,] + public short decodeRsaPrivateKey(short version) { + short resp = KMArray.instance((short) 3); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_INTEGER); + short modulus = KMByteBlob.instance(len); + getBytes(modulus); + updateRsaKeyBuffer(modulus); + len = header(ASN1_INTEGER); + short pubKey = KMByteBlob.instance(len); + getBytes(pubKey); + len = header(ASN1_INTEGER); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + updateRsaKeyBuffer(privKey); + KMArray.cast(resp).add((short) 0, modulus); + KMArray.cast(resp).add((short) 1, pubKey); + KMArray.cast(resp).add((short) 2, privKey); + return resp; + } + + private void updateRsaKeyBuffer(short blob) { + byte[] buffer = KMByteBlob.cast(blob).getBuffer(); + short startOff = KMByteBlob.cast(blob).getStartOff(); + short len = KMByteBlob.cast(blob).length(); + if (0 == buffer[startOff] && len > 256) { + KMByteBlob.cast(blob).setStartOff(++startOff); + KMByteBlob.cast(blob).setLength(--len); + } + } + + private short readEcdsa256SigIntegerHeader() { + short len = header(ASN1_INTEGER); + if (len == 33) { + if (0 != getByte()) { + KMException.throwIt(KMError.INVALID_DATA); + } + len--; + } else if (len > 33) { + KMException.throwIt(KMError.INVALID_DATA); + } + return len; + } + + // Seq [Int, Int] + public short decodeEcdsa256Signature(short blob, byte[] scratchPad, short scratchPadOff) { + init(blob); + short len = header(ASN1_SEQUENCE); + len = readEcdsa256SigIntegerHeader(); + // concatenate r and s in the buffer (r||s) + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 64, (byte) 0); + // read r + getBytes(scratchPad, (short) (scratchPadOff + 32 - len), len); + len = readEcdsa256SigIntegerHeader(); + // read s + getBytes(scratchPad, (short) (scratchPadOff + 64 - len), len); + return (short) 64; + } + + // Seq [Int, Blob] + public void decodeCommon(short version, byte[] alg) { + short len = header(ASN1_SEQUENCE); + len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_SEQUENCE); + short blob = KMByteBlob.instance(len); + getBytes(blob); + if (Util.arrayCompare( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + alg, + (short) 0, + KMByteBlob.cast(blob).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + // Seq[Int,blob,blob] + public short decodeEcPrivateKey(short version) { + short resp = KMArray.instance((short) 2); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_OCTET_STRING); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + validateTag0IfPresent(); + header(ASN1_A1_TAG); + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + KMArray.cast(resp).add((short) 0, pubKey); + KMArray.cast(resp).add((short) 1, privKey); + return resp; + } + + private void validateTag0IfPresent() { + if (data[dataInfo[DATA_CURSOR_OFFSET]] != ASN1_A0_TAG) { + return; + } + ; + short len = header(ASN1_A0_TAG); + if (len != EC_CURVE.length) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (Util.arrayCompare(data, dataInfo[DATA_CURSOR_OFFSET], EC_CURVE, (short) 0, len) != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(len); + } + + private void validateAttributeTypeAndValue() { + // First byte should be OBJECT_IDENTIFIER, otherwise it is not well-formed DER Subject. + if (data[dataInfo[DATA_CURSOR_OFFSET]] != OBJECT_IDENTIFIER) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Check if the OID matches the email address + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + EMAIL_ADDRESS_OID, + (short) 0, + (short) EMAIL_ADDRESS_OID.length) + == 0)) { + incrementCursor((short) EMAIL_ADDRESS_OID.length); + // Validate the length of the attribute value. + if (getByte() != IA5_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short emailLength = getLength(); + if (emailLength <= 0 && emailLength > MAX_EMAIL_ADD_LEN) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(emailLength); + return; + } + // Check other OIDs. + for (short i = 0; i < (short) attributeOIds.length; i++) { + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + COMMON_OID, + (short) 0, + (short) COMMON_OID.length) + == 0) + && (attributeOIds[i] + == data[(short) (dataInfo[DATA_CURSOR_OFFSET] + COMMON_OID.length)])) { + incrementCursor((short) (COMMON_OID.length + 1)); + // Validate the length of the attribute value. + short tag = getByte(); + if (tag != ASN1_UTF8_STRING + && tag != ASN1_TELETEX_STRING + && tag != ASN1_PRINTABLE_STRING + && tag != ASN1_UNIVERSAL_STRING + && tag != ASN1_BMP_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short attrValueLength = getLength(); + if (attrValueLength <= 0 && attrValueLength > attributeValueMaxLen[i]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(attrValueLength); + return; + } + } + // If no match is found above then move the cursor to next element. + getByte(); // Move Cursor by one byte (OID) + incrementCursor(getLength()); // Move cursor to AtrributeTag + getByte(); // Move cursor to AttributeValue + incrementCursor(getLength()); // Move cursor to next SET element + } + + private short header(short tag) { + short t = getByte(); + if (t != tag) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return getLength(); + } + + private byte getByte() { + byte d = data[dataInfo[DATA_CURSOR_OFFSET]]; + incrementCursor((short) 1); + return d; + } + + private short getShort() { + short d = Util.getShort(data, dataInfo[DATA_CURSOR_OFFSET]); + incrementCursor((short) 2); + return d; + } + + private void getBytes(short blob) { + short len = KMByteBlob.cast(blob).length(); + Util.arrayCopyNonAtomic( + data, + dataInfo[DATA_CURSOR_OFFSET], + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + len); + incrementCursor(len); + } + + private void getBytes(byte[] buffer, short offset, short len) { + Util.arrayCopyNonAtomic(data, dataInfo[DATA_CURSOR_OFFSET], buffer, offset, len); + incrementCursor(len); + } + + private short getLength() { + byte len = getByte(); + if (len >= 0) { + return len; + } + len = (byte) (len & 0x7F); + if (len == 1) { + return (short) (getByte() & 0xFF); + } else if (len == 2) { + return getShort(); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMType.INVALID_VALUE; // should not come here + } + + public void init(short blob) { + data = KMByteBlob.cast(blob).getBuffer(); + dataInfo[DATA_START_OFFSET] = KMByteBlob.cast(blob).getStartOff(); + dataInfo[DATA_LENGTH_OFFSET] = KMByteBlob.cast(blob).length(); + dataInfo[DATA_CURSOR_OFFSET] = dataInfo[DATA_START_OFFSET]; + } + + public void incrementCursor(short n) { + dataInfo[DATA_CURSOR_OFFSET] += n; + if (dataInfo[DATA_CURSOR_OFFSET] + > ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java new file mode 100644 index 0000000..5eb7eae --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java @@ -0,0 +1,110 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBignumTag represents BIGNUM Tag Type from android keymaster hal specifications. The tag value + * of this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BIGNUM_TAG; short tagKey; short blobPtr}} + */ +public class KMBignumTag extends KMTag { + + private static KMBignumTag prototype; + + private KMBignumTag() {} + + private static KMBignumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBignumTag(); + } + KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMBignumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BIGNUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case CERTIFICATE_SERIAL_NUM: + if (valueLen > MAX_CERTIFICATE_SERIAL_SIZE) { + return false; + } + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BIGNUM_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java new file mode 100644 index 0000000..27730a5 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -0,0 +1,115 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBoolTag represents BOOL TAG type from the android keymaster hal specifications. If it is + * present in the key parameter list then its value is always true. A KMTag always requires a value + * because it is a key value pair. The bool tag always has 0x01 as its value. struct{byte TAG_TYPE; + * short length; struct{short BOOL_TAG; short tagKey; byte value 1}} + */ +public class KMBoolTag extends KMTag { + + // The allowed tag keys of type bool tag. + private static final short[] tags = { + CALLER_NONCE, + INCLUDE_UNIQUE_ID, + BOOTLOADER_ONLY, + ROLLBACK_RESISTANCE, + NO_AUTH_REQUIRED, + ALLOW_WHILE_ON_BODY, + TRUSTED_USER_PRESENCE_REQUIRED, + TRUSTED_CONFIRMATION_REQUIRED, + UNLOCKED_DEVICE_REQUIRED, + RESET_SINCE_ID_ROTATION, + EARLY_BOOT_ONLY, + DEVICE_UNIQUE_ATTESTATION + }; + private static KMBoolTag prototype; + + private KMBoolTag() {} + + private static KMBoolTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBoolTag(); + } + KMType.instanceTable[KM_BOOL_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateKey(key)) { + KMException.throwIt(KMError.INVALID_TAG); + } + short ptr = KMType.instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + // Value is always 1. + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = 0x01; + return ptr; + } + + public static KMBoolTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BOOL_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // validate the tag key. + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + public static short[] getTags() { + return tags; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BOOL_TAG; + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java new file mode 100644 index 0000000..98d81fc --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -0,0 +1,142 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteBlob represents contiguous block of bytes. It corresponds to CBOR type of Byte String. It + * extends KMType by specifying value field as zero or more sequence of bytes. struct{byte + * BYTE_BLOB_TYPE; short length; sequence of bytes} + */ +public class KMByteBlob extends KMType { + + private static byte OFFSET_SIZE = 2; + private static KMByteBlob prototype; + + protected KMByteBlob() {} + + private static KMByteBlob proto(short ptr) { + if (prototype == null) { + prototype = new KMByteBlob(); + } + KMType.instanceTable[KM_BYTE_BLOB_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(BYTE_BLOB_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(BYTE_BLOB_TYPE, (short) (length + 2)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMByteBlob + public static KMByteBlob cast(short ptr) { + if (heap[ptr] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // Add the byte + public void add(short index, byte val) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + heap[(short) (getStartOff() + index)] = val; + } + + // Get the byte + public byte get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return heap[(short) (getStartOff() + index)]; + } + + // Get the start of blob + public short getStartOff() { + return Util.getShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE)); + } + + public void setStartOff(short offset) { + Util.setShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE), offset); + } + + // Get the length of the blob + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + public void getValue(byte[] destBuf, short destStart, short destLength) { + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + } + + public short getValues(byte[] destBuf, short destStart) { + short destLength = length(); + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + return destLength; + } + + public void setValue(byte[] srcBuf, short srcStart, short srcLength) { + if (length() < srcLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.arrayCopyNonAtomic(srcBuf, srcStart, heap, getStartOff(), srcLength); + setLength(srcLength); + } + + public boolean isValid() { + return (length() != 0); + } + + protected short getBaseOffset() { + return instanceTable[KM_BYTE_BLOB_OFFSET]; + } + + public void setLength(short len) { + Util.setShort(heap, (short) (getBaseOffset() + 1), len); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java new file mode 100644 index 0000000..5f7231f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -0,0 +1,146 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteTag represents BYTES Tag Type from android keymaster hal specifications. The tag value of + * this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BYTES_TAG; short tagKey; short blobPtr}} + */ +public class KMByteTag extends KMTag { + + private static KMByteTag prototype; + + private KMByteTag() {} + + private static KMByteTag proto(short ptr) { + if (prototype == null) { + prototype = new KMByteTag(); + } + KMType.instanceTable[KM_BYTE_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMByteTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BYTES_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case ATTESTATION_APPLICATION_ID: + if (valueLen > MAX_ATTESTATION_APP_ID_SIZE) { + return false; + } + break; + case CERTIFICATE_SUBJECT_NAME: + { + if (valueLen > KMConfigurations.MAX_SUBJECT_DER_LEN) { + return false; + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + asn1Decoder.validateDerSubject(byteBlob); + } + break; + case APPLICATION_ID: + case APPLICATION_DATA: + if (valueLen > MAX_APP_ID_APP_DATA_SIZE) { + return false; + } + break; + case ATTESTATION_CHALLENGE: + if (valueLen > MAX_ATTESTATION_CHALLENGE_SIZE) { + return false; + } + break; + case ATTESTATION_ID_BRAND: + case ATTESTATION_ID_DEVICE: + case ATTESTATION_ID_PRODUCT: + case ATTESTATION_ID_SERIAL: + case ATTESTATION_ID_IMEI: + case ATTESTATION_ID_SECOND_IMEI: + case ATTESTATION_ID_MEID: + case ATTESTATION_ID_MANUFACTURER: + case ATTESTATION_ID_MODEL: + if (valueLen > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + return false; + } + break; + case ROOT_OF_TRUST: + case NONCE: + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BYTES_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java new file mode 100644 index 0000000..bcfb5f8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -0,0 +1,513 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; + +/** + * This class constructs the Cose messages like CoseKey, CoseMac0, MacStructure, CoseSign1, + * SignStructure, CoseEncrypt, EncryptStructure and ReceipientStructures. + */ +public class KMCose { + + // COSE SIGN1 + public static final byte COSE_SIGN1_ENTRY_COUNT = 4; + public static final byte COSE_SIGN1_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_SIGN1_PAYLOAD_OFFSET = 2; + public static final byte COSE_SIGN1_SIGNATURE_OFFSET = 3; + // COSE MAC0 + public static final byte COSE_MAC0_ENTRY_COUNT = 4; + public static final byte COSE_MAC0_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_MAC0_PAYLOAD_OFFSET = 2; + public static final byte COSE_MAC0_TAG_OFFSET = 3; + // COSE ENCRYPT + public static final byte COSE_ENCRYPT_ENTRY_COUNT = 4; + public static final byte COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT = 3; + public static final byte COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT = 3; + + // COSE Labels + public static final byte COSE_LABEL_ALGORITHM = 1; + public static final byte COSE_LABEL_KEYID = 4; + public static final byte COSE_LABEL_IV = 5; + public static final byte COSE_LABEL_COSE_KEY = (byte) 0xFF; // -1 + + // COSE Algorithms + public static final byte COSE_ALG_AES_GCM_256 = 3; // AES-GCM mode w/ 256-bit key, 128-bit tag. + public static final byte COSE_ALG_HMAC_256 = 5; // HMAC w/ SHA-256 + public static final byte COSE_ALG_ES256 = (byte) 0xF9; // ECDSA w/ SHA-256; -7 + public static final byte COSE_ALG_ECDH_ES_HKDF_256 = (byte) 0xE7; // ECDH-EC+HKDF-256; -25 + + // COSE P256 EC Curve + public static final byte COSE_ECCURVE_256 = 1; + + // COSE key types + public static final byte COSE_KEY_TYPE_EC2 = 2; + public static final byte COSE_KEY_TYPE_SYMMETRIC_KEY = 4; + + // COSE Key Operations + public static final byte COSE_KEY_OP_SIGN = 1; + public static final byte COSE_KEY_OP_VERIFY = 2; + public static final byte COSE_KEY_OP_ENCRYPT = 3; + public static final byte COSE_KEY_OP_DECRYPT = 4; + + // AES GCM + public static final short AES_GCM_KEY_SIZE_BITS = 256; + // Cose key parameters. + public static final byte COSE_KEY_KEY_TYPE = 1; + public static final byte COSE_KEY_KEY_ID = 2; + public static final byte COSE_KEY_ALGORITHM = 3; + public static final byte COSE_KEY_CURVE = -1; + public static final byte COSE_KEY_PUBKEY_X = -2; + public static final byte COSE_KEY_PUBKEY_Y = -3; + public static final byte COSE_KEY_PRIV_KEY = -4; + public static final byte[] COSE_TEST_KEY = { + (byte) 0xFF, (byte) 0xFE, (byte) 0xEE, (byte) 0x90 + }; // -70000 + public static final byte COSE_KEY_MAX_SIZE = 4; + + // kdfcontext strings + public static final byte[] client = {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + public static final byte[] server = {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + // Context strings + public static final byte[] MAC_CONTEXT = {0x4d, 0x41, 0x43, 0x30}; // MAC0 + public static final byte[] SIGNATURE1_CONTEXT = { + 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31 + }; // Signature1 + // Certificate payload supported keys + public static final byte ISSUER = (byte) 0x01; + public static final byte SUBJECT = (byte) 0x02; + public static final byte[] SUBJECT_PUBLIC_KEY = { + (byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8 + }; + public static final byte[] KEY_USAGE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + public static final byte[] TEST_ISSUER_NAME = { + (byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72 + }; // "Issuer" + public static final byte[] TEST_SUBJECT_NAME = { + 0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74 + }; // "Subject" + public static final byte[] KEY_USAGE_SIGN = {0x20}; // Key usage sign + + public static final short[] COSE_KEY_LABELS = { + KMCose.COSE_KEY_KEY_TYPE, + KMCose.COSE_KEY_KEY_ID, + KMCose.COSE_KEY_ALGORITHM, + KMCose.COSE_KEY_CURVE, + KMCose.COSE_KEY_PUBKEY_X, + KMCose.COSE_KEY_PUBKEY_Y, + KMCose.COSE_KEY_PRIV_KEY + }; + public static final short[] COSE_HEADER_LABELS = { + KMCose.COSE_LABEL_ALGORITHM, + KMCose.COSE_LABEL_KEYID, + KMCose.COSE_LABEL_IV, + KMCose.COSE_LABEL_COSE_KEY + }; + + /** + * Constructs the Cose MAC structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the external Aad. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @return KMArray instance of MAC structure. + */ + public static short constructCoseMacStructure( + short protectedHeader, short extAad, short payload) { + // Create MAC Structure and compute HMAC as per https://tools.ietf.org/html/rfc8152#section-6.3 + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.MAC_CONTEXT, (short) 0, (short) KMCose.MAC_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_MAC0 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unprotectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @param tag Bstr pointer which holds the tag value. + * @return KMArray instance of COSE_MAC0 object. + */ + public static short constructCoseMac0( + short protectedHeader, short unprotectedHeader, short payload, short tag) { + // Construct Cose_MAC0 + // COSE_Mac0 = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // tag : bstr, + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, tag); + // Do encode. + return arrPtr; + } + + /** + * Constructs the COSE_Signature structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the aad. + * @param payload Bstr pointer which holds the payload. + * @return KMArray instance of COSE_Signature object. + */ + public static short constructCoseSignStructure( + short protectedHeader, short extAad, short payload) { + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.SIGNATURE1_CONTEXT, (short) 0, (short) KMCose.SIGNATURE1_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_Sign1 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unProtectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload. + * @param signature Bstr pointer which holds the signature. + * @return KMArray instance of COSE_Sign1 object. + */ + public static short constructCoseSign1( + short protectedHeader, short unProtectedHeader, short payload, short signature) { + // COSE_Sign = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // signatures : [+ COSE_Signature] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, signature); + return arrPtr; + } + + /** + * Constructs array based on the tag values provided. + * + * @param tag array of tag values to be constructed. + * @param includeTestMode flag which indicates if TEST_COSE_KEY should be included or not. + * @return instance of KMArray. + */ + private static short handleCosePairTags( + short[] tag, short[] keyValues, short valueIndex, boolean includeTestMode) { + short index = 0; + // var is used to calculate the length of the array. + short var = 0; + short tagLen = (short) tag.length; + // var is used to calculate the length of the array. + while (index < tagLen) { + if (keyValues[index] != KMType.INVALID_VALUE) { + keyValues[(short) (index + valueIndex)] = + buildCosePairTag((byte) tag[index], keyValues[index]); + var++; + } + index++; + } + var += includeTestMode ? 1 : 0; + short arrPtr = KMArray.instance(var); + index = 0; + // var is used to index the array. + var = 0; + while (index < tagLen) { + if (keyValues[(short) (index + valueIndex)] != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).add(var++, keyValues[(short) (index + valueIndex)]); + } + index++; + } + return arrPtr; + } + + /** + * Constructs the COSE_sign1 payload for certificate. + * + * @param issuer instance of KMCosePairTextStringTag which contains issuer value. + * @param subject instance of KMCosePairTextStringTag which contains subject value. + * @param subPublicKey instance of KMCosePairByteBlobTag which contains encoded KMCoseKey. + * @param keyUsage instance of KMCosePairByteBlobTag which contains key usage value. + * @return instance of KMArray. + */ + public static short constructCoseCertPayload( + short issuer, short subject, short subPublicKey, short keyUsage) { + short certPayload = KMArray.instance((short) 4); + KMArray.cast(certPayload).add((short) 0, issuer); + KMArray.cast(certPayload).add((short) 1, subject); + KMArray.cast(certPayload).add((short) 2, subPublicKey); + KMArray.cast(certPayload).add((short) 3, keyUsage); + certPayload = KMCoseCertPayload.instance(certPayload); + KMCoseCertPayload.cast(certPayload).canonicalize(); + return certPayload; + } + + /** + * Construct headers structure. Headers can be part of COSE_Sign1, COSE_Encrypt, COSE_Mac0 and + * COSE_Key. + * + * @param alg instance of either KMNInteger or KMInteger, based on the sign of algorithm value. + * @param keyId instance of KMByteBlob which contains the key identifier. + * @param iv instance of KMByteblob which contains the iv buffer. + * @param ephemeralKey instance of KMCoseKey. + * @return instance of KMCoseHeaders. + */ + public static short constructHeaders( + short[] buff, short alg, short keyId, short iv, short ephemeralKey) { + buff[0] = alg; + buff[1] = keyId; + buff[2] = iv; + buff[3] = ephemeralKey; + for (short i = 4; i < 8; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short ptr = handleCosePairTags(COSE_HEADER_LABELS, buff, (short) 4, false); + ptr = KMCoseHeaders.instance(ptr); + KMCoseHeaders.cast(ptr).canonicalize(); + return ptr; + } + + /** + * Constructs the instance of KMCosePair*Tag. + * + * @param key value of the key. + * @param valuePtr instance of one of KMType. + * @return instance of KMCosePair*Value object. + */ + public static short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getType(valuePtr); + short keyPtr; + if (key < 0) { + keyPtr = KMNInteger.uint_8(key); + } else { + keyPtr = KMInteger.uint_8(key); + } + switch (type) { + case KMType.INTEGER_TYPE: + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + case KMType.NEG_INTEGER_TYPE: + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + case KMType.BYTE_BLOB_TYPE: + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + case KMType.TEXT_STRING_TYPE: + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + case KMType.COSE_KEY_TYPE: + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + /** + * Constructs a CoseKey with the provided input parameters. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType Instance of the identification of the key type. + * @param keyId Instance of key identification value. + * @param keyAlg Instance of the algorithm that is used with this key. + * @param curve Instance of the EC curve that is used with this key. + * @param pubKey Buffer containing the public key. + * @param pubKeyOff Start offset of the buffer. + * @param pubKeyLen Length of the public key. + * @param privKeyPtr Instance of the private key. + * @param testMode Represents if key is used in test mode or production mode. + * @return Instance of the CoseKey structure. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + short privKeyPtr, + boolean testMode) { + if (pubKey[pubKeyOff] == 0x04) { // uncompressed format + pubKeyOff += 1; + pubKeyLen -= 1; + } + pubKeyLen = (short) (pubKeyLen / 2); + short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); + short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); + short coseKey = + constructCoseKey(buff, keyType, keyId, keyAlg, curve, xPtr, yPtr, privKeyPtr, testMode); + KMCoseKey.cast(coseKey).canonicalize(); + return coseKey; + } + + /** + * Constructs the cose key based on input parameters supplied. All the parameters must be + * instantiated from either KMInteger or KMNInteger or KMByteblob types. + * + * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. + * @param keyId instance of KMByteBlob which holds key identifier value. + * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. + * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. + * @param pubX instance of KMByteBlob which holds EC public key's x value. + * @param pubY instance of KMByteBlob which holds EC public key's y value. + * @param priv instance of KMByteBlob which holds EC private value. + * @param includeTestKey flag which identifies whether to construct test key or production key. + * @return instance of the KMCoseKey object. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + short pubX, + short pubY, + short priv, + boolean includeTestKey) { + short valueIndex = 7; + buff[0] = keyType; + buff[1] = keyId; + buff[2] = keyAlg; + buff[3] = curve; + buff[4] = pubX; + buff[5] = pubY; + buff[6] = priv; + for (short i = valueIndex; i < 16; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short arrPtr = handleCosePairTags(COSE_KEY_LABELS, buff, valueIndex, includeTestKey); + if (includeTestKey) { + short testKey = + KMCosePairSimpleValueTag.instance( + KMNInteger.uint_32(KMCose.COSE_TEST_KEY, (short) 0), + KMSimpleValue.instance(KMSimpleValue.NULL)); + KMArray.cast(arrPtr).add((short) (KMArray.cast(arrPtr).length() - 1), testKey); + } + arrPtr = KMCoseKey.instance(arrPtr); + KMCoseKey.cast(arrPtr).canonicalize(); + return arrPtr; + } + + /** + * Constructs key derivation context which is required to compute HKDF. + * + * @param publicKeyA public key buffer from the first party. + * @param publicKeyAOff start position of the public key buffer from first party. + * @param publicKeyALen length of the public key buffer from first party. + * @param publicKeyB public key buffer from the second party. + * @param publicKeyBOff start position of the public key buffer from second party. + * @param publicKeyBLen length of the public key buffer from second party. + * @param senderIsA true if caller is first party, false if caller is second party. + * @return instance of KMArray. + */ + public static short constructKdfContext( + byte[] publicKeyA, + short publicKeyAOff, + short publicKeyALen, + byte[] publicKeyB, + short publicKeyBOff, + short publicKeyBLen, + boolean senderIsA) { + short index = 0; + // Prepare sender info + short senderInfo = KMArray.instance((short) 3); + KMArray.cast(senderInfo) + .add(index++, KMByteBlob.instance(client, (short) 0, (short) client.length)); + KMArray.cast(senderInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(senderInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen) + : KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen)); + + // Prepare recipient info + index = 0; + short recipientInfo = KMArray.instance((short) 3); + KMArray.cast(recipientInfo) + .add(index++, KMByteBlob.instance(server, (short) 0, (short) server.length)); + KMArray.cast(recipientInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(recipientInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen) + : KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen)); + + // supply public info + index = 0; + short publicInfo = KMArray.instance((short) 2); + KMArray.cast(publicInfo).add(index++, KMInteger.uint_16(AES_GCM_KEY_SIZE_BITS)); + KMArray.cast(publicInfo).add(index, KMByteBlob.instance((short) 0)); + + // construct kdf context + index = 0; + short arrPtr = KMArray.instance((short) 4); + KMArray.cast(arrPtr).add(index++, KMInteger.uint_8(COSE_ALG_AES_GCM_256)); + KMArray.cast(arrPtr).add(index++, senderInfo); + KMArray.cast(arrPtr).add(index++, recipientInfo); + KMArray.cast(arrPtr).add(index, publicInfo); + + return arrPtr; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java new file mode 100644 index 0000000..fff9cf8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java @@ -0,0 +1,136 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseCertPayload represents the COSE_Sign1 payload for each certificate in BCC. The supported + * key types are KMInteger, KMNInteger and the supported value types are KMByteBlob and + * KMTextString. It corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short + * arrayPtr } where arrayPtr is a pointer to array with any KMCosePairTagType subtype instances. + */ +public class KMCoseCertPayload extends KMCoseMap { + + private static KMCoseCertPayload prototype; + + private KMCoseCertPayload() {} + + private static KMCoseCertPayload proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseCertPayload(); + } + instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairTextStringTag.exp()); + arr.add((short) 1, KMCosePairByteBlobTag.exp()); + return KMCoseCertPayload.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_CERT_PAYLOAD_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseCertPayload cast(short ptr) { + if (heap[ptr] != COSE_CERT_PAYLOAD_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + keyPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getSubjectPublicKey() { + return getValueType( + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 2), // LSB + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 0) // MSB (Significant) + ); + } + + public short getSubject() { + return getValueType(KMCose.SUBJECT, KMType.INVALID_VALUE); + } + + public short getIssuer() { + return getValueType(KMCose.ISSUER, KMType.INVALID_VALUE); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java new file mode 100644 index 0000000..0e722d2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java @@ -0,0 +1,203 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseHeaders represents headers section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-3. The supported key types are KMInteger, + * KMNInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMCoseKey. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseHeaders extends KMCoseMap { + + private static KMCoseHeaders prototype; + + private KMCoseHeaders() {} + + private static KMCoseHeaders proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseHeaders(); + } + instanceTable[KM_COSE_HEADERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + // CoseKey is internally an Array so evaluate it separately. + short coseKeyValueExp = KMCosePairCoseKeyTag.exp(); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, coseKeyValueExp); + return KMCoseHeaders.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_HEADERS_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseHeaders cast(short ptr) { + if (heap[ptr] != COSE_HEADERS_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_HEADERS_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key) { + short index = 0; + short len = length(); + short arr = getVals(); + short tagType; + short valPtr = 0; + short keyPtr; + boolean found = false; + while (index < len) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + keyPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_LABEL_KEYID); + } + + public short getCoseKey() { + return getValueType(KMCose.COSE_LABEL_COSE_KEY); + } + + public short getIV() { + return getValueType(KMCose.COSE_LABEL_IV); + } + + public short getAlgorithm() { + return getValueType(KMCose.COSE_LABEL_ALGORITHM); + } + + public boolean isDataValid(short[] buff, short alg, short keyIdPtr) { + short bufLen = 4; + buff[0] = KMCose.COSE_LABEL_ALGORITHM; + buff[1] = alg; + buff[2] = KMCose.COSE_LABEL_KEYID; + buff[3] = keyIdPtr; + boolean valid = false; + short value; + short ptr; + short tagIndex = 0; + while (tagIndex < bufLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex]); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + default: + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java new file mode 100644 index 0000000..d1bfec1 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -0,0 +1,255 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseKey represents COSE_Key section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-7 The supported key types are KMNInteger, + * KMInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMSimpleValue. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. Note that construction of the + * key_ops label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + */ +public class KMCoseKey extends KMCoseMap { + + private static KMCoseKey prototype; + + private KMCoseKey() {} + + private static KMCoseKey proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseKey(); + } + instanceTable[KM_COSE_KEY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, KMCosePairSimpleValueTag.exp()); + return KMCoseKey.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_KEY_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseKey cast(short ptr) { + if (heap[ptr] != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_KEY_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_KEY_KEY_ID, KMType.INVALID_VALUE); + } + + public short getEcdsa256PublicKey(byte[] pubKey, short pubKeyOff) { + short baseOffset = pubKeyOff; + pubKey[pubKeyOff] = (byte) 0x04; // uncompressed. + pubKeyOff++; + short ptr = getValueType(KMCose.COSE_KEY_PUBKEY_X, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + ptr = getValueType(KMCose.COSE_KEY_PUBKEY_Y, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + return (short) (pubKeyOff - baseOffset); + } + + public short getPrivateKey(byte[] priv, short privOff) { + short ptr = getValueType(KMCose.COSE_KEY_PRIV_KEY, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + priv, + privOff, + KMByteBlob.cast(ptr).length()); + return KMByteBlob.cast(ptr).length(); + } + + public boolean isTestKey() { + short ptr = + getValueType( + Util.getShort(KMCose.COSE_TEST_KEY, (short) 2), // LSB + Util.getShort(KMCose.COSE_TEST_KEY, (short) 0) // MSB (Significant) + ); + boolean isTestKey = false; + if (ptr != 0) { + isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + } + return isTestKey; + } + + /** + * Verifies the KMCoseKey values against the input values. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType value of the key type + * @param keyIdPtr instance of KMByteBlob containing the key id. + * @param keyAlg value of the algorithm. + * @param keyOps value of the key operations. + * @param curve value of the curve. + * @return true if valid, otherwise false. + */ + public boolean isDataValid( + short[] buff, short keyType, short keyIdPtr, short keyAlg, short curve) { + short buffLen = 8; + buff[0] = KMCose.COSE_KEY_KEY_TYPE; + buff[1] = keyType; + buff[2] = KMCose.COSE_KEY_KEY_ID; + buff[3] = keyIdPtr; + buff[4] = KMCose.COSE_KEY_ALGORITHM; + buff[5] = keyAlg; + buff[6] = KMCose.COSE_KEY_CURVE; + buff[7] = curve; + boolean valid = false; + short ptr; + short tagIndex = 0; + short value; + while (tagIndex < buffLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex], KMType.INVALID_VALUE); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java new file mode 100644 index 0000000..5d373a7 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java @@ -0,0 +1,171 @@ +/* + * 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" (short)0IS, + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class represents either a Cose_key or Cose headers as defined in + * https://datatracker.ietf.org/doc/html/rfc8152 This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * KMCoseHeaders and KMCoseKey implements this class. + */ +public abstract class KMCoseMap extends KMType { + + public static byte[] scratchpad; + + /** + * This function creates an instance of either KMCoseHeaders or KMCoseKey based on the type + * information provided. + * + * @param typePtr type information of the underlying KMType. + * @param arrPtr instance of KMArray. + * @return instance type of either KMCoseHeaders or KMCoseKey. + */ + public static short createInstanceFromType(short typePtr, short arrPtr) { + short mapType = KMType.getType(typePtr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.instance(arrPtr); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.instance(arrPtr); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.instance(arrPtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + public static short getVals(short ptr) { + short mapType = KMType.getType(ptr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.cast(ptr).getVals(); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.cast(ptr).getVals(); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.cast(ptr).getVals(); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private static short getKey(short tagPtr) { + short tagType = KMCosePairTagType.getTagValueType(tagPtr); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return KMCosePairByteBlobTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return KMCosePairIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return KMCosePairNegIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return KMCosePairSimpleValueTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return KMCosePairCoseKeyTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return KMCosePairTextStringTag.cast(tagPtr).getKeyPtr(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + private static void createScratchBuffer() { + if (scratchpad == null) { + scratchpad = JCSystem.makeTransientByteArray((short) 120, JCSystem.CLEAR_ON_RESET); + } + } + + protected static void canonicalize(short arr) { + canonicalize(arr, KMArray.cast(arr).length()); + } + + private static void swap(short ptr, short firstIndex, short secondIndex) { + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + KMArray.cast(ptr).swap(firstIndex, secondIndex); + } else { + KMMap.cast(ptr).swap(firstIndex, secondIndex); + } + } + + private static boolean compareAndSwap(short ptr, short index) { + short firstKey; + short secondKey; + short firstKeyLen; + short secondKeyLen; + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + firstKey = getKey(KMArray.cast(ptr).get(index)); + secondKey = getKey(KMArray.cast(ptr).get((short) (index + 1))); + } else { // Map + firstKey = KMMap.cast(ptr).getKey(index); + secondKey = KMMap.cast(ptr).getKey((short) (index + 1)); + } + firstKeyLen = + KMKeymasterApplet.encoder.encode( + firstKey, scratchpad, (short) 0, (short) scratchpad.length); + secondKeyLen = + KMKeymasterApplet.encoder.encode( + secondKey, scratchpad, firstKeyLen, (short) scratchpad.length); + if ((firstKeyLen > secondKeyLen) + || ((firstKeyLen == secondKeyLen) + && (0 + < Util.arrayCompare( + scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) { + swap(ptr, index, (short) (index + 1)); + return true; + } + return false; + } + + /** + * Canonicalizes using bubble sort. + * + * @param ptr instance pointer of either array or map. + * @param length length of the array or map instance. + */ + public static void canonicalize(short ptr, short length) { + short index = 0; + short innerIndex = 0; + createScratchBuffer(); + boolean swapped; + while (index < length) { + swapped = false; + innerIndex = 0; + while (innerIndex < (short) (length - index - 1)) { + swapped |= compareAndSwap(ptr, innerIndex); + innerIndex++; + } + if (!swapped) { + break; + } + index++; + } + } + + public abstract short getVals(); + + public abstract short length(); + + public abstract void canonicalize(); +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java new file mode 100644 index 0000000..04c3abe --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java @@ -0,0 +1,137 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairByteBlobTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMByteBlob type. struct{byte TAG_TYPE; short length; struct{short BYTE_BLOB_TYPE; short + * key; short value}}. + */ +public class KMCosePairByteBlobTag extends KMCosePairTagType { + + public static Object[] keys; + private static KMCosePairByteBlobTag prototype; + + private KMCosePairByteBlobTag() {} + + private static KMCosePairByteBlobTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairByteBlobTag(); + } + instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMByteBlob.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(keyPtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairByteBlobTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static void createKeys() { + if (keys == null) { + keys = + new Object[] { + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_X}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_Y}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PRIV_KEY}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_IV}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_KEYID}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_KEY_ID}, + (Object) KMCose.SUBJECT_PUBLIC_KEY, + (Object) KMCose.KEY_USAGE + }; + } + } + + public static boolean isKeyValueValid(short keyPtr) { + createKeys(); + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short index = 0; + while (index < (short) keys.length) { + if (0 + == Util.arrayCompare( + (byte[]) keys[index], + (short) 0, + heap, + offset, + (short) ((byte[]) keys[index]).length)) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return BYTE_BLOB_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java new file mode 100644 index 0000000..5290da2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java @@ -0,0 +1,89 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairCoseKeyTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMCoseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairCoseKeyTag extends KMCosePairTagType { + + public static final byte[] keys = {KMCose.COSE_LABEL_COSE_KEY}; + private static KMCosePairCoseKeyTag prototype; + + private KMCosePairCoseKeyTag() {} + + private static KMCosePairCoseKeyTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairCoseKeyTag(); + } + instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMCoseKey.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairCoseKeyTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return COSE_KEY_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java new file mode 100644 index 0000000..ea052a6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java @@ -0,0 +1,92 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMInteger type. struct{byte TAG_TYPE; short length; struct{short INT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairIntegerTag extends KMCosePairTagType { + + private static KMCosePairIntegerTag prototype; + + private KMCosePairIntegerTag() {} + + private static KMCosePairIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairIntegerTag(); + } + instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMInteger.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java new file mode 100644 index 0000000..7f01202 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java @@ -0,0 +1,92 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairNegIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMNInteger type. struct{byte TAG_TYPE; short length; struct{short NINT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairNegIntegerTag extends KMCosePairTagType { + + private static KMCosePairNegIntegerTag prototype; + + private KMCosePairNegIntegerTag() {} + + private static KMCosePairNegIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairNegIntegerTag(); + } + instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMNInteger.exp()); + return ptr; + } + + public static KMCosePairNegIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (NEG_INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMNInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public short getValueType() { + return NEG_INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java new file mode 100644 index 0000000..a0d7da8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java @@ -0,0 +1,76 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairSimpleValueTag represents a key-value type, where key can be KMInteger or KMNInteger + * and value is KMSimpleValue type. struct{byte TAG_TYPE; short length; struct{short + * SIMPLE_VALUE_TYPE; short key; short value}}. + */ +public class KMCosePairSimpleValueTag extends KMCosePairTagType { + + private static KMCosePairSimpleValueTag prototype; + + private KMCosePairSimpleValueTag() {} + + private static KMCosePairSimpleValueTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairSimpleValueTag(); + } + instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMSimpleValue.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMSimpleValue.cast(valuePtr).getValue())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairSimpleValueTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return SIMPLE_VALUE_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java new file mode 100644 index 0000000..baa0855 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -0,0 +1,248 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * This class represents the a key-value types. This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * The structure representing all the sub classes of KMCosePairTagType is as follows: + * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), + * Value(2 bytes). Key can be either KMInteger or KMNInteger and Value can be either KMIntger or + * KMNinteger or KMSimpleValue or KMByteBlob or KMTextString or KMCoseKey. Each subclass of + * KMCosePairTagType is named after their corresponding value type of the Cose pair. + */ +public abstract class KMCosePairTagType extends KMType { + + /** + * Below table represents the allowed values for a key. The maximum length of the key can be 4 + * bytes so each key is represented as 4 bytes. The allowed values are placed next to their + * corresponding key. + */ + public static Object[] allowedKeyPairs; + + private static void createAllowedKeyPairs() { + if (allowedKeyPairs == null) { + allowedKeyPairs = + new Object[] { + // Key type + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_TYPE}, + (Object) new byte[] {KMCose.COSE_KEY_TYPE_EC2, KMCose.COSE_KEY_TYPE_SYMMETRIC_KEY}, + // Key Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + KMCose.COSE_ALG_ES256 + }, + // Key Curve + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_CURVE}, + (Object) new byte[] {KMCose.COSE_ECCURVE_256}, + // Header Label Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_LABEL_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256 + }, + // Test Key + KMCose.COSE_TEST_KEY, + (Object) new byte[] {KMSimpleValue.NULL}, + }; + } + } + + /** + * Validates the key and the values corresponding to key. + * + * @param key Buffer containing the key. + * @param keyOff Offset in the buffer from where key starts. + * @param keyLen Length of the key buffer. + * @param value Value corresponding to the key. + * @return true if key pair is valid, otherwise false. + */ + public static boolean isKeyPairValid(byte[] key, short keyOff, short keyLen, short value) { + short index = 0; + short valueIdx; + byte[] values; + boolean valid = false; + createAllowedKeyPairs(); + while (index < allowedKeyPairs.length) { + valueIdx = 0; + if (isEqual( + (byte[]) allowedKeyPairs[index], + (short) 0, + (short) ((byte[]) allowedKeyPairs[index]).length, + key, + keyOff, + keyLen)) { + values = (byte[]) allowedKeyPairs[(short) (index + 1)]; + while (valueIdx < values.length) { + if (values[valueIdx] == (byte) value) { + valid = true; + break; + } + valueIdx++; + } + if (valid) { + break; + } + } + index += (short) 2; + } + return valid; + } + + /** + * Compares two key buffers. + * + * @param key1 First buffer containing the key. + * @param offset1 Offset of the first buffer. + * @param length1 Length of the first buffer. + * @param key2 Second buffer containing the key. + * @param offset2 Offset of the second buffer. + * @param length2 Length of the second buffer. + * @return true if both keys are equal, otherwise false. + */ + private static boolean isEqual( + byte[] key1, short offset1, short length1, byte[] key2, short offset2, short length2) { + if (length1 != length2) { + return false; + } + return (0 == KMInteger.unsignedByteArrayCompare(key1, offset1, key2, offset2, length1)); + } + + /** + * Returns the short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + /** + * Returns the significant short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueSignificantShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getSignificantShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getSignificantShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { + short type = KMType.getType(keyPtr); + if (type == INTEGER_TYPE) { + KMInteger.cast(keyPtr).getValue(dest, offset, len); + } else if (type == NEG_INTEGER_TYPE) { + KMNInteger.cast(keyPtr).getValue(dest, offset, len); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + /** + * Returns the key offset from the key pointer. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return offset from where the key starts. + */ + public static short getKeyStartOffset(short keyPtr) { + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return offset; + } + + /** + * Returns the key length. + * + * @param keyPtr pointer to either KMInteger/KMInteger. + * @return length of the key. + */ + public static short getKeyLength(short keyPtr) { + short type = KMType.getType(keyPtr); + short len = 0; + if (type == INTEGER_TYPE) { + len = KMInteger.cast(keyPtr).length(); + } else if (type == NEG_INTEGER_TYPE) { + len = KMNInteger.cast(keyPtr).length(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + /** + * This function returns one of COSE_KEY_TAG_*_VALUE_TYPE tag information. + * + * @param ptr Pointer to one of the KMCoseKey*Value class. + * @return Tag value type. + */ + public static short getTagValueType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + /** + * This function returns the key pointer. + * + * @return key pointer. + */ + public abstract short getKeyPtr(); + + /** + * This function returns the value pointer. + * + * @return value pointer. + */ + public abstract short getValuePtr(); +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java new file mode 100644 index 0000000..99506b6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java @@ -0,0 +1,91 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairTextStringTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMTextString type. struct{byte TAG_TYPE; short length; struct{short TXT_STR_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairTextStringTag extends KMCosePairTagType { + + public static final byte[] keys = { + KMCose.ISSUER, KMCose.SUBJECT, + }; + private static KMCosePairTextStringTag prototype; + + private KMCosePairTextStringTag() {} + + private static KMCosePairTextStringTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairTextStringTag(); + } + instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMTextString.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairTextStringTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return TEXT_STRING_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java new file mode 100644 index 0000000..dc0101a --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -0,0 +1,781 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class decodes the CBOR format data into a KMType structure. It interprets the input CBOR + * format using the input expression provided. Validation of KeyMint tags and tag types happens in + * the process of decoding, while constructing the subtype of a KMType structure. + */ +public class KMDecoder { + + // major types + private static final short UINT_TYPE = 0x00; + private static final short NEG_INT_TYPE = 0x20; + private static final short BYTES_TYPE = 0x40; + private static final short TSTR_TYPE = 0x60; + private static final short ARRAY_TYPE = 0x80; + private static final short MAP_TYPE = 0xA0; + private static final short SIMPLE_VALUE_TYPE = 0xE0; + private static final short SEMANTIC_TAG_TYPE = 0xC0; + + // masks + private static final short ADDITIONAL_MASK = 0x1F; + private static final short MAJOR_TYPE_MASK = 0xE0; + + // value length + private static final short UINT8_LENGTH = 0x18; + private static final short UINT16_LENGTH = 0x19; + private static final short UINT32_LENGTH = 0x1A; + private static final short UINT64_LENGTH = 0x1B; + + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte TAG_KEY_OFFSET = 4; + private Object[] bufferRef; + private short[] scratchBuf; + + public KMDecoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[TAG_KEY_OFFSET] = (short) 0; + } + + public short decode(short expression, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + return decode(expression); + } + + public short decodeArray(short exp, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short expLength = KMArray.cast(exp).length(); + if (payloadLength > expLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short index = 0; + short obj; + short type; + short arrPtr = KMArray.instance(payloadLength); + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + return arrPtr; + } + + private short decode(short exp) { + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + return decodeByteBlob(exp); + case KMType.TEXT_STRING_TYPE: + return decodeTstr(exp); + case KMType.INTEGER_TYPE: + return decodeInteger(exp); + case KMType.SIMPLE_VALUE_TYPE: + return decodeSimpleValue(exp); + case KMType.SEMANTIC_TAG_TYPE: + return decodeSemanticTagValue(exp); + case KMType.NEG_INTEGER_TYPE: + return decodeNegInteger(exp); + case KMType.ARRAY_TYPE: + return decodeArray(exp); + case KMType.MAP_TYPE: + return decodeMap(exp); + case KMType.ENUM_TYPE: + return decodeEnum(exp); + case KMType.KEY_PARAM_TYPE: + return decodeKeyParam(exp); + case KMType.KEY_CHAR_TYPE: + return decodeKeyChar(exp); + case KMType.VERIFICATION_TOKEN_TYPE: + return decodeVerificationToken(exp); + case KMType.HMAC_SHARING_PARAM_TYPE: + return decodeHmacSharingParam(exp); + case KMType.HW_AUTH_TOKEN_TYPE: + return decodeHwAuthToken(exp); + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + return decodeCoseMap(exp); + case KMType.COSE_PAIR_TAG_TYPE: + short tagValueType = KMCosePairTagType.getTagValueType(exp); + return decodeCosePairTag(tagValueType, exp); + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + return decodeTag(tagType, exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + return decodeBignumTag(exp); + case KMType.BYTES_TAG: + return decodeBytesTag(exp); + case KMType.BOOL_TAG: + return decodeBoolTag(exp); + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + return decodeIntegerTag(exp); + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + return decodeIntegerArrayTag(exp); + case KMType.ENUM_TAG: + return decodeEnumTag(exp); + case KMType.ENUM_ARRAY_TAG: + return decodeEnumArrayTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeVerificationToken(short exp) { + short vals = decode(KMVerificationToken.cast(exp).getVals()); + return KMVerificationToken.instance(vals); + } + + private short decodeHwAuthToken(short exp) { + short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + return KMHardwareAuthToken.instance(vals); + } + + private short decodeHmacSharingParam(short exp) { + short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + return KMHmacSharingParameters.instance(vals); + } + + private short decodeKeyChar(short exp) { + short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + return KMKeyCharacteristics.instance(vals); + } + + private short decodeCosePairKey(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short keyPtr = (short) 0; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) == UINT_TYPE) { + keyPtr = decodeInteger(exp); + } else if ((buffer[startOff] & MAJOR_TYPE_MASK) == NEG_INT_TYPE) { + keyPtr = decodeNegInteger(exp); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return keyPtr; + } + + private short decodeCosePairSimpleValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairSimpleValueTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairSimpleValueTag.cast(exp).getValuePtr()); + return KMCosePairSimpleValueTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairIntegerValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairIntegerTag.cast(exp).getValuePtr()); + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairNegIntegerTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairNegIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairNegIntegerTag.cast(exp).getValuePtr()); + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairTxtStringTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairTextStringTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairTextStringTag.cast(exp).getValuePtr()); + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairCoseKeyTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairCoseKeyTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairCoseKeyTag.cast(exp).getValuePtr()); + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairByteBlobTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairByteBlobTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairByteBlobTag.cast(exp).getValuePtr()); + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + } + + private short peekCosePairTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // This decoder is confined to support only key and value types which are required for remote + // key provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr / + // tstr / Cosekey) only are supported. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE + && (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + short additionalMask = (short) (buffer[startOff] & ADDITIONAL_MASK); + short increment = 0; + if (additionalMask < UINT8_LENGTH) { + increment++; + } else if (additionalMask == UINT8_LENGTH) { + increment += 2; + } else if (additionalMask == UINT16_LENGTH) { + increment += 3; + } else if (additionalMask == UINT32_LENGTH) { + increment += 5; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short majorType = (short) (buffer[(short) (startOff + increment)] & MAJOR_TYPE_MASK); + short tagValueType = 0; + if (majorType == BYTES_TYPE) { + tagValueType = KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE; + } else if (majorType == UINT_TYPE) { + tagValueType = KMType.COSE_PAIR_INT_TAG_TYPE; + } else if (majorType == NEG_INT_TYPE) { + tagValueType = KMType.COSE_PAIR_NEG_INT_TAG_TYPE; + } else if (majorType == MAP_TYPE) { + tagValueType = KMType.COSE_PAIR_COSE_KEY_TAG_TYPE; + } else if (majorType == SIMPLE_VALUE_TYPE) { + tagValueType = KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE; + } else if (majorType == TSTR_TYPE) { + tagValueType = KMType.COSE_PAIR_TEXT_STR_TAG_TYPE; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return tagValueType; + } + + private short decodeCosePairTag(short tagValueType, short exp) { + switch (tagValueType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return decodeCosePairByteBlobTag(exp); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return decodeCosePairNegIntegerTag(exp); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return decodeCosePairIntegerValueTag(exp); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return decodeCosePairSimpleValueTag(exp); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return decodeCosePairCoseKeyTag(exp); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return decodeCosePairTxtStringTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeCoseMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // get allowed key pairs + short allowedKeyPairs = KMCoseMap.getVals(exp); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedKeyPairs).length(); + short index = 0; + boolean tagFound; + short tagInd; + short cosePairTagType; + short tagClass; + short allowedType; + short obj; + + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + cosePairTagType = peekCosePairTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + allowedType = KMCosePairTagType.getTagValueType(tagClass); + if (allowedType == cosePairTagType) { + obj = decode(tagClass); + KMArray.cast(vals).add(index, obj); + tagFound = true; + break; + } + tagInd++; + } + if (!tagFound) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else { + index++; + } + } + return KMCoseMap.createInstanceFromType(exp, vals); + } + + private short decodeKeyParam(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // allowed tags + short allowedTags = KMKeyParameters.cast(exp).getVals(); + short tagRule = KMArray.cast(allowedTags).get((short) 0); + boolean ignoreInvalidTags = KMEnum.cast(tagRule).getVal() == KMType.IGNORE_INVALID_TAGS; + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedTags).length(); + short index = 0; + boolean tagFound; + short tagInd; + short tagType; + short tagClass; + short allowedType; + short obj; + short arrPos = 0; + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 1; + tagType = peekTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedTags).get(tagInd); + allowedType = KMTag.getTagType(tagClass); + // If it is part of allowed tags ... + if (tagType == allowedType) { + // then decodeByteBlob and add that to the array. + try { + tagFound = true; + obj = decode(tagClass); + KMArray.cast(vals).add(arrPos++, obj); + break; + } catch (KMException e) { + if (KMException.reason() == KMError.INVALID_TAG) { + if (!ignoreInvalidTags) { + KMException.throwIt(KMError.INVALID_TAG); + } + } else { + KMException.throwIt(KMException.reason()); + } + break; + } + } + tagInd++; + } + if (!tagFound) { + KMException.throwIt(KMError.INVALID_TAG); + } else { + index++; + } + } + KMArray.cast(vals).setLength(arrPos); + return KMKeyParameters.instance(vals); + } + + private short decodeEnumArrayTag(short exp) { + readTagKey(KMEnumArrayTag.cast(exp).getTagType()); + return KMEnumArrayTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerArrayTag(short exp) { + readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); + // the values are array of integers. + return KMIntegerArrayTag.instance( + KMIntegerArrayTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerTag(short exp) { + readTagKey(KMIntegerTag.cast(exp).getTagType()); + // the value is an integer + return KMIntegerTag.instance( + KMIntegerTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerTag.cast(exp).getValue())); + } + + private short decodeBytesTag(short exp) { + readTagKey(KMByteTag.cast(exp).getTagType()); + // The value must be byte blob + return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.cast(exp).getValue())); + } + + private short decodeBignumTag(short exp) { + readTagKey(KMBignumTag.cast(exp).getTagType()); + // The value must be byte blob + return KMBignumTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.cast(exp).getValue())); + } + + private short decodeMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + short mapPtr = KMMap.instance(payloadLength); + short index = 0; + short type; + short keyobj; + short valueobj; + while (index < payloadLength) { + type = KMMap.cast(exp).getKey(index); + keyobj = decode(type); + type = KMMap.cast(exp).getKeyValue(index); + valueobj = decode(type); + KMMap.cast(mapPtr).add(index, keyobj, valueobj); + index++; + } + return mapPtr; + } + + private short decodeArray(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short arrPtr = KMArray.instance(payloadLength); + short index = 0; + short type; + short obj; + // check whether array contains one type of objects or multiple types + if (KMArray.cast(exp).containedType() + == KMType.INVALID_VALUE) { // multiple types specified by expression. + if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.cast(exp).length() != payloadLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + } + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } else { // Array is a Vector containing objects of one type + type = KMArray.cast(exp).containedType(); + while (index < payloadLength) { + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } + return arrPtr; + } + + private short decodeEnumTag(short exp) { + readTagKey(KMEnumTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum Tag value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal = 0; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else if (len == UINT8_LENGTH) { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnumTag.instance(scratchBuf[TAG_KEY_OFFSET], enumVal); + } + + private short decodeBoolTag(short exp) { + readTagKey(KMBoolTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + incrementStartOff((short) 1); + return KMBoolTag.instance(scratchBuf[TAG_KEY_OFFSET]); + } + + private short decodeEnum(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); + } + + private short decodeSimpleValue(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byte addInfo = (byte) (buffer[startOff] & ADDITIONAL_MASK); + incrementStartOff((short) 1); + return KMSimpleValue.instance(addInfo); + } + + private short decodeSemanticTagValue(short exp) { + // Decode tag. + short tag = readMajorTypeWithInteger(exp, SEMANTIC_TAG_TYPE, UINT32_LENGTH); + // Decode value pointer. + short valuePtr = decode(KMSemanticTag.cast(exp).getValuePtr()); + return KMSemanticTag.instance(tag, valuePtr); + } + + private short readMajorTypeWithInteger(short exp, short majorType, short maxLimit) { + short inst; + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > maxLimit) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + if (len < UINT8_LENGTH) { + inst = KMInteger.uint_8((byte) (len & ADDITIONAL_MASK)); + } else if (len == UINT8_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 1); + incrementStartOff((short) 1); + } else if (len == UINT16_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 2); + incrementStartOff((short) 2); + } else if (len == UINT32_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 4); + incrementStartOff((short) 4); + } else { + inst = KMInteger.instance(buffer, startOff, (short) 8); + incrementStartOff((short) 8); + } + return inst; + } + + private short decodeInteger(short exp) { + return readMajorTypeWithInteger(exp, UINT_TYPE, UINT64_LENGTH); + } + + private short decodeNegIntegerValue(byte addInfo, byte[] buf, short startOffset) { + short inst; + short len = 0; + short scratchpad; + if (addInfo < UINT8_LENGTH) { + addInfo = (byte) (-1 - addInfo); + inst = KMNInteger.uint_8(addInfo); + } else { + switch (addInfo) { + case UINT8_LENGTH: + len = 1; + break; + case UINT16_LENGTH: + len = 2; + break; + case UINT32_LENGTH: + len = 4; + break; + case UINT64_LENGTH: + len = 8; + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // Do (-1 - N), as per cbor negative integer decoding rule. + // N is the integer value. + scratchpad = KMByteBlob.instance((short) (len * 3)); + byte[] input = KMByteBlob.cast(scratchpad).getBuffer(); + short offset = KMByteBlob.cast(scratchpad).getStartOff(); + Util.arrayFillNonAtomic(input, offset, len, (byte) -1); + Util.arrayCopyNonAtomic(buf, startOffset, input, (short) (offset + len), len); + KMUtils.subtract( + input, offset, (short) (offset + len), (short) (offset + 2 * len), (byte) len); + inst = KMNInteger.instance(input, (short) (offset + 2 * len), len); + incrementStartOff(len); + } + return inst; + } + + private short decodeNegInteger(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > UINT64_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + return decodeNegIntegerValue((byte) len, buffer, startOff); + } + + private short decodeTstr(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(TSTR_TYPE); + short inst = + KMTextString.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short decodeByteBlob(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(BYTES_TYPE); + short inst = + KMByteBlob.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short peekTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + if ((short) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return (short) + ((Util.makeShort(buffer[(short) (startOff + 1)], buffer[(short) (startOff + 2)])) + & KMType.TAG_TYPE_MASK); + } + + private void readTagKey(short expectedTagType) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + short tagType = readShort(); + scratchBuf[TAG_KEY_OFFSET] = readShort(); + if (tagType != expectedTagType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + // payload length cannot be more then 16 bits. + private short readMajorTypeWithPayloadLength(short majorType) { + short payloadLength; + byte val = readByte(); + if ((short) (val & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short lenType = (short) (val & ADDITIONAL_MASK); + if (lenType > UINT16_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (lenType < UINT8_LENGTH) { + payloadLength = lenType; + } else if (lenType == UINT8_LENGTH) { + payloadLength = (short) (readByte() & 0xFF); + } else { + payloadLength = readShort(); + } + return payloadLength; + } + + private short readShort() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short val = Util.makeShort(buffer[startOff], buffer[(short) (startOff + 1)]); + incrementStartOff((short) 2); + return val; + } + + private byte readByte() { + short startOff = scratchBuf[START_OFFSET]; + byte val = ((byte[]) bufferRef[0])[startOff]; + incrementStartOff((short) 1); + return val; + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] > scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short readKeyblobVersion(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + short arrayLen = readMajorTypeWithPayloadLength(ARRAY_TYPE); + if (arrayLen == 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short version = KMType.INVALID_VALUE; + try { + version = decodeInteger(KMInteger.exp()); + } catch (Exception e) { + // Fail to decode Integer. It can happen if it is an old KeyBlob. + } + return version; + } + + public short readCertificateChainHeaderLen(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + readMajorTypeWithPayloadLength(BYTES_TYPE); + return (short) (scratchBuf[START_OFFSET] - bufOffset); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java new file mode 100644 index 0000000..941a0ac --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -0,0 +1,785 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class encodes KMType structures to a cbor format data recursively. Encoded bytes are written + * on the buffer provided by the caller. An exception will be thrown if the encoded data length is + * greater than the buffer length provided. + */ +public class KMEncoder { + + // major types + private static final byte UINT_TYPE = 0x00; + private static final byte NEG_INT_TYPE = 0x20; + private static final byte BYTES_TYPE = 0x40; + private static final byte TSTR_TYPE = 0x60; + private static final byte ARRAY_TYPE = (byte) 0x80; + private static final byte MAP_TYPE = (byte) 0xA0; + private static final byte SIMPLE_VALUE_TYPE = (byte) 0xE0; + private static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + + // masks + private static final byte ADDITIONAL_MASK = 0x1F; + + // value length + private static final byte UINT8_LENGTH = (byte) 0x18; + private static final byte UINT16_LENGTH = (byte) 0x19; + private static final byte UINT32_LENGTH = (byte) 0x1A; + private static final byte UINT64_LENGTH = (byte) 0x1B; + private static final short TINY_PAYLOAD = 0x17; + private static final short SHORT_PAYLOAD = 0x100; + private static final byte STACK_SIZE = 50; + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte STACK_PTR_OFFSET = 4; + + private Object[] bufferRef; + private short[] scratchBuf; + private short[] stack; + + public KMEncoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + stack = JCSystem.makeTransientShortArray(STACK_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[STACK_PTR_OFFSET] = (short) 0; + } + + private void push(short objPtr) { + stack[scratchBuf[STACK_PTR_OFFSET]] = objPtr; + scratchBuf[STACK_PTR_OFFSET]++; + } + + private short pop() { + scratchBuf[STACK_PTR_OFFSET]--; + return stack[scratchBuf[STACK_PTR_OFFSET]]; + } + + private void encode(short obj) { + push(obj); + } + + /** + * This functions encodes the given object into the provider buffer space in cbor format. + * + * @param object Object to be encoded into cbor data. + * @param buffer Output where cbor data is copied. + * @param startOff is the start offset of the buffer. + * @param bufLen length of the buffer + * @param encoderOutLimitLen excepted encoded output length. + * @return length of the encoded buffer. + */ + public short encode( + short object, byte[] buffer, short startOff, short bufLen, short encoderOutLimitLen) { + scratchBuf[STACK_PTR_OFFSET] = 0; + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + if ((short) (startOff + encoderOutLimitLen) > bufLen) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + scratchBuf[LEN_OFFSET] = (short) (startOff + encoderOutLimitLen); + push(object); + encode(); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + public short encode(short object, byte[] buffer, short startOff, short bufLen) { + return encode(object, buffer, startOff, bufLen, (short) (bufLen - startOff)); + } + + // array{KMError.OK,Array{KMByteBlobs}} + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { + if (bufferStart > certStart) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + bufferRef[0] = certBuffer; + scratchBuf[START_OFFSET] = certStart; + scratchBuf[LEN_OFFSET] = (short) (certStart + 1); + // Byte Header + cert length + scratchBuf[START_OFFSET] -= getEncodedBytesLength(certLength); + // Array header - 1 elements i.e. 1 byte + scratchBuf[START_OFFSET]--; + if (scratchBuf[START_OFFSET] < bufferStart) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + bufferStart = scratchBuf[START_OFFSET]; + writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 elements + writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length + return bufferStart; + } + + private void encode() { + while (scratchBuf[STACK_PTR_OFFSET] > 0) { + short exp = pop(); + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + encodeByteBlob(exp); + break; + case KMType.TEXT_STRING_TYPE: + encodeTextString(exp); + break; + case KMType.INTEGER_TYPE: + encodeUnsignedInteger(exp); + break; + case KMType.SIMPLE_VALUE_TYPE: + encodeSimpleValue(exp); + break; + case KMType.NEG_INTEGER_TYPE: + encodeNegInteger(exp); + break; + case KMType.ARRAY_TYPE: + encodeArray(exp); + break; + case KMType.MAP_TYPE: + encodeMap(exp); + break; + case KMType.ENUM_TYPE: + encodeEnum(exp); + break; + case KMType.KEY_PARAM_TYPE: + encodeKeyParam(exp); + break; + case KMType.SEMANTIC_TAG_TYPE: + encodeSemanticTag(exp); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + encodeCoseMap(exp); + break; + case KMType.KEY_CHAR_TYPE: + encodeKeyChar(exp); + break; + case KMType.VERIFICATION_TOKEN_TYPE: + encodeVeriToken(exp); + break; + case KMType.HMAC_SHARING_PARAM_TYPE: + encodeHmacSharingParam(exp); + break; + case KMType.HW_AUTH_TOKEN_TYPE: + encodeHwAuthToken(exp); + break; + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + encodeTag(tagType, exp); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(exp); + encodeCosePairTag(cosePairTagType, exp); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + } + + private void encodeCosePairIntegerTag(short exp) { + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairIntTag.getValuePtr()); + encode(cosePairIntTag.getKeyPtr()); + } + + private void encodeCosePairByteBlobTag(short exp) { + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairByteBlobTag.getValuePtr()); + encode(cosePairByteBlobTag.getKeyPtr()); + } + + private void encodeCosePairCoseKeyTag(short exp) { + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairCoseKeyTag.getValuePtr()); + encode(cosePairCoseKeyTag.getKeyPtr()); + } + + private void encodeCosePairTextStringTag(short exp) { + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairTextStringTag.getValuePtr()); + encode(cosePairTextStringTag.getKeyPtr()); + } + + private void encodeCosePairSimpleValueTag(short exp) { + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairSimpleValueTag.getValuePtr()); + encode(cosePairSimpleValueTag.getKeyPtr()); + } + + private void encodeCosePairNegIntegerTag(short exp) { + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairNegIntegerTag.getValuePtr()); + encode(cosePairNegIntegerTag.getKeyPtr()); + } + + private void encodeCosePairTag(short tagType, short exp) { + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + encodeCosePairByteBlobTag(exp); + return; + case KMType.COSE_PAIR_INT_TAG_TYPE: + encodeCosePairIntegerTag(exp); + return; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + encodeCosePairNegIntegerTag(exp); + return; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + encodeCosePairSimpleValueTag(exp); + return; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + encodeCosePairTextStringTag(exp); + return; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + encodeCosePairCoseKeyTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + encodeBignumTag(exp); + return; + case KMType.BYTES_TAG: + encodeBytesTag(exp); + return; + case KMType.BOOL_TAG: + encodeBoolTag(exp); + return; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + encodeIntegerTag(exp); + return; + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + encodeIntegerArrayTag(exp); + return; + case KMType.ENUM_TAG: + encodeEnumTag(exp); + return; + case KMType.ENUM_ARRAY_TAG: + encodeEnumArrayTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeCoseMap(short obj) { + encodeAsMap(KMCoseMap.getVals(obj)); + } + + private void encodeKeyParam(short obj) { + encodeAsMap(KMKeyParameters.cast(obj).getVals()); + } + + private void encodeKeyChar(short obj) { + encode(KMKeyCharacteristics.cast(obj).getVals()); + } + + private void encodeVeriToken(short obj) { + encode(KMVerificationToken.cast(obj).getVals()); + } + + private void encodeHwAuthToken(short obj) { + encode(KMHardwareAuthToken.cast(obj).getVals()); + } + + private void encodeHmacSharingParam(short obj) { + encode(KMHmacSharingParameters.cast(obj).getVals()); + } + + private void encodeArray(short obj) { + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short subObj; + while (index >= 0) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + encode(subObj); + } + index--; + } + } + + public void encodeArrayOnlyLength(short arrLength, byte[] buffer, short offset, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = offset; + scratchBuf[LEN_OFFSET] = (short) (offset + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, length); + } + + private void encodeMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMMap.cast(obj).length()); + short len = KMMap.cast(obj).length(); + short index = (short) (len - 1); + while (index >= 0) { + encode(KMMap.cast(obj).getKeyValue(index)); + encode(KMMap.cast(obj).getKey(index)); + index--; + } + } + + private void encodeAsMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short inst; + while (index >= 0) { + inst = KMArray.cast(obj).get(index); + encode(inst); + index--; + } + } + + private void encodeIntegerArrayTag(short obj) { + writeTag(KMIntegerArrayTag.cast(obj).getTagType(), KMIntegerArrayTag.cast(obj).getKey()); + encode(KMIntegerArrayTag.cast(obj).getValues()); + } + + private void encodeEnumArrayTag(short obj) { + writeTag(KMEnumArrayTag.cast(obj).getTagType(), KMEnumArrayTag.cast(obj).getKey()); + encode(KMEnumArrayTag.cast(obj).getValues()); + } + + private void encodeIntegerTag(short obj) { + writeTag(KMIntegerTag.cast(obj).getTagType(), KMIntegerTag.cast(obj).getKey()); + encode(KMIntegerTag.cast(obj).getValue()); + } + + private void encodeBignumTag(short obj) { + writeTag(KMBignumTag.getTagType(obj), KMBignumTag.getKey(obj)); + encode(KMBignumTag.cast(obj).getValue()); + } + + private void encodeBytesTag(short obj) { + writeTag(KMByteTag.cast(obj).getTagType(), KMByteTag.cast(obj).getKey()); + encode(KMByteTag.cast(obj).getValue()); + } + + private void encodeBoolTag(short obj) { + writeTag(KMBoolTag.cast(obj).getTagType(), KMBoolTag.cast(obj).getKey()); + writeByteValue(KMBoolTag.cast(obj).getVal()); + } + + private void encodeEnumTag(short obj) { + writeTag(KMEnumTag.cast(obj).getTagType(), KMEnumTag.cast(obj).getKey()); + writeByteValue(KMEnumTag.cast(obj).getValue()); + } + + private void encodeEnum(short obj) { + writeByteValue(KMEnum.cast(obj).getVal()); + } + + private void encodeInteger(byte[] val, short len, short startOff, short majorType) { + // find out the most significant byte + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + if (diff == 0) { + writeByte((byte) (majorType | 0)); + } else if ((diff == 1) + && (val[(short) (startOff + msbIndex)] < UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + writeByte((byte) (majorType | val[(short) (startOff + msbIndex)])); + } else if (diff == 1) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte(val[(short) (startOff + msbIndex)]); + } else if (diff == 2) { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeBytes(val, (short) (startOff + msbIndex), (short) 2); + } else if (diff <= 4) { + writeByte((byte) (majorType | UINT32_LENGTH)); + writeBytes(val, (short) (startOff + len - 4), (short) 4); + } else { + writeByte((byte) (majorType | UINT64_LENGTH)); + writeBytes(val, startOff, (short) 8); + } + } + + // find out the most significant byte + public short findMsb(byte[] buf, short offset, short len) { + byte index = 0; + // find out the most significant byte + while (index < len) { + if (buf[(short) (offset + index)] > 0) { + break; + } else if (buf[(short) (offset + index)] < 0) { + break; + } + index++; // index will be equal to len if value is 0. + } + return index; + } + + public void computeOnesCompliment(short msbIndex, byte[] buf, short offset, short len) { + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + short correctedOffset = offset; + short correctedLen = len; + // The offset and length of the buffer for Short and Byte types should be + // corrected before computing the 1s compliment. The reason for doing this + // is to avoid computation of 1s compliment on the MSB bytes. + if (diff == 0) { + // Fail + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else if (diff == 1) { + correctedOffset = (short) (offset + 3); + correctedLen = 1; + } else if (diff == 2) { + correctedOffset = (short) (offset + 2); + correctedLen = 2; + } + // For int and long values the len and offset values are always proper. + // int - 4 bytes + // long - 8 bytes. + KMUtils.computeOnesCompliment(buf, correctedOffset, correctedLen); + } + + // Encoding rule for negative Integers is taken from + // https://datatracker.ietf.org/doc/html/rfc7049#section-2.1, Major type 1. + public short handleNegIntegerEncodingRule(byte[] buf, short offset, short len) { + short msbIndex = findMsb(buf, offset, len); + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(msbIndex, buf, offset, len); + return msbIndex; + } + + // Note: This function modifies the buffer's actual value. So after encoding, restore the original + // value by calling removeNegIntegerEncodingRule(). + public short applyNegIntegerEncodingRule(byte[] buf, short offset, short len) { + return handleNegIntegerEncodingRule(buf, offset, len); + } + + public void removeNegIntegerEncodingRule( + byte[] buf, short offset, short len, short origMsbIndex) { + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(origMsbIndex, buf, offset, len); + } + + private void encodeNegInteger(short obj) { + byte[] val = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short startOff = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(val, startOff, len); + encodeInteger(val, len, startOff, NEG_INT_TYPE); + removeNegIntegerEncodingRule(val, startOff, len, msbIndex); + } + + private void encodeSemanticTag(short obj) { + short tag = KMSemanticTag.cast(obj).getKeyPtr(); + encode(KMSemanticTag.cast(obj).getValuePtr()); + encodeInteger( + KMInteger.cast(tag).getBuffer(), + KMInteger.cast(tag).length(), + KMInteger.cast(tag).getStartOff(), + SEMANTIC_TAG_TYPE); + } + + private void encodeUnsignedInteger(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + encodeInteger(val, len, startOff, UINT_TYPE); + } + + private void encodeSimpleValue(short obj) { + byte value = KMSimpleValue.cast(obj).getValue(); + writeByte((byte) (SIMPLE_VALUE_TYPE | value)); + } + + private void encodeTextString(short obj) { + writeMajorTypeWithLength(TSTR_TYPE, KMTextString.cast(obj).length()); + writeBytes( + KMTextString.cast(obj).getBuffer(), + KMTextString.cast(obj).getStartOff(), + KMTextString.cast(obj).length()); + } + + public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(BYTES_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + private void encodeByteBlob(short obj) { + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.cast(obj).length()); + writeBytes( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + + public short getEncodedLength(short ptr) { + short len = 0; + short type = KMType.getType(ptr); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + len += getEncodedByteBlobLength(ptr); + break; + case KMType.TEXT_STRING_TYPE: + len += getEncodedTextStringLength(ptr); + break; + case KMType.INTEGER_TYPE: + len += getEncodedIntegerLength(ptr); + break; + case KMType.NEG_INTEGER_TYPE: + len += getEncodedNegIntegerLength(ptr); + break; + case KMType.ARRAY_TYPE: + len += getEncodedArrayLen(ptr); + break; + case KMType.MAP_TYPE: + len += getEncodedMapLen(ptr); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(ptr); + len += getEncodedCosePairTagLen(cosePairTagType, ptr); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + len += getEncodedArrayLen(KMCoseMap.getVals(ptr)); + break; + default: + KMException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + private short getEncodedCosePairTagLen(short tagType, short exp) { + short length = 0; + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + length = getEncodedLength(cosePairByteBlobTag.getKeyPtr()); + length += getEncodedLength(cosePairByteBlobTag.getValuePtr()); + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + length = getEncodedLength(cosePairIntTag.getValuePtr()); + length += getEncodedLength(cosePairIntTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + length = getEncodedLength(cosePairNegIntegerTag.getValuePtr()); + length += getEncodedLength(cosePairNegIntegerTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + length = getEncodedLength(cosePairSimpleValueTag.getValuePtr()); + length += getEncodedLength(cosePairSimpleValueTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + length = getEncodedLength(cosePairTextStringTag.getValuePtr()); + length += getEncodedLength(cosePairTextStringTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + length = getEncodedLength(cosePairCoseKeyTag.getValuePtr()); + length += getEncodedLength(cosePairCoseKeyTag.getKeyPtr()); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return length; + } + + private short getEncodedMapLen(short obj) { + short mapLen = KMMap.cast(obj).length(); + short len = getEncodedBytesLength(mapLen); + short index = 0; + while (index < mapLen) { + len += getEncodedLength(KMMap.cast(obj).getKey(index)); + len += getEncodedLength(KMMap.cast(obj).getKeyValue(index)); + index++; + } + return len; + } + + private short getEncodedArrayLen(short obj) { + short arrLen = KMArray.cast(obj).length(); + short len = getEncodedBytesLength(arrLen); + short index = 0; + short subObj; + while (index < arrLen) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + len += getEncodedLength(subObj); + } + index++; + } + return len; + } + + public short getEncodedBytesLength(short len) { + short ret = 0; + if (len < KMEncoder.UINT8_LENGTH && len >= 0) { + ret = 1; + } else if (len >= KMEncoder.UINT8_LENGTH && len <= (short) 0x00FF) { + ret = 2; + } else if (len > (short) 0x00FF && len <= (short) 0x7FFF) { + ret = 3; + } else { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return ret; + } + + private short getEncodedByteBlobLength(short obj) { + short len = KMByteBlob.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedTextStringLength(short obj) { + short len = KMTextString.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedNegIntegerLength(short obj) { + byte[] buf = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short offset = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(buf, offset, len); + short ret = getEncodedIntegerLength(buf, offset, len); + removeNegIntegerEncodingRule(buf, offset, len, msbIndex); + return ret; + } + + private short getEncodedIntegerLength(byte[] val, short startOff, short len) { + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + switch (diff) { + case 0: + case 1: // Byte + if ((val[(short) (startOff + msbIndex)] < KMEncoder.UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + return (short) 1; + } else { + return (short) 2; + } + case 2: // Short + return (short) 3; + case 3: + case 4: // UInt32 + return (short) 5; + case 5: + case 6: + case 7: + case 8: // UInt64 + return (short) 9; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return 0; + } + + private short getEncodedIntegerLength(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + return getEncodedIntegerLength(val, startOff, len); + } + + private void writeByteValue(byte val) { + if ((val < UINT8_LENGTH) && (val >= 0)) { + writeByte((byte) (UINT_TYPE | val)); + } else { + writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); + writeByte(val); + } + } + + private void writeTag(short tagType, short tagKey) { + writeByte((byte) (UINT_TYPE | UINT32_LENGTH)); + writeShort(tagType); + writeShort(tagKey); + } + + private void writeMajorTypeWithLength(byte majorType, short len) { + if (len <= TINY_PAYLOAD) { + writeByte((byte) (majorType | (byte) (len & ADDITIONAL_MASK))); + } else if (len < SHORT_PAYLOAD) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte((byte) (len & 0xFF)); + } else { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeShort(len); + } + } + + private void writeBytes(byte[] buf, short start, short len) { + byte[] buffer = (byte[]) bufferRef[0]; + Util.arrayCopyNonAtomic(buf, start, buffer, scratchBuf[START_OFFSET], len); + incrementStartOff(len); + } + + private void writeShort(short val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = (byte) ((val >> 8) & 0xFF); + incrementStartOff((short) 1); + buffer[scratchBuf[START_OFFSET]] = (byte) ((val & 0xFF)); + incrementStartOff((short) 1); + } + + private void writeByte(byte val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = val; + incrementStartOff((short) 1); + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] >= scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short encodeArrayHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java new file mode 100644 index 0000000..44bf477 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -0,0 +1,166 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnum represents an enumeration specified in android keymaster hal specifications. It + * corresponds to uint CBOR type and it is a byte value. struct{byte ENUM_TYPE; short length; + * struct{short enumType; byte val}} + */ +public class KMEnum extends KMType { + + private static KMEnum prototype; + + // The allowed enum types. + private static short[] types = { + HARDWARE_TYPE, + KEY_FORMAT, + KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, + DEVICE_LOCKED, + USER_AUTH_TYPE, + PURPOSE, + ECCURVE, + RULE + }; + + private static Object[] enums = null; + + private KMEnum() {} + + private static KMEnum proto(short ptr) { + if (prototype == null) { + prototype = new KMEnum(); + } + KMType.instanceTable[KM_ENUM_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(ENUM_TYPE); + } + + public static KMEnum cast(short ptr) { + if (heap[ptr] != ENUM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short enumType) { + if (!validateEnum(enumType, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + return ptr; + } + + public static short instance(short enumType, byte val) { + if (!validateEnum(enumType, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 3); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + heap[(short) (ptr + TLV_HEADER_SIZE + 2)] = val; + return ptr; + } + + private static void create() { + // The allowed enum values to corresponding enum types in the types array. + if (enums == null) { + enums = + new Object[] { + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX}, + new byte[] {X509, PKCS8, RAW}, + new byte[] { + DERIVATION_NONE, + RFC5869_SHA256, + ISO18033_2_KDF1_SHA1, + ISO18033_2_KDF1_SHA256, + ISO18033_2_KDF2_SHA1, + ISO18033_2_KDF2_SHA256 + }, + new byte[] {SELF_SIGNED_BOOT, VERIFIED_BOOT, UNVERIFIED_BOOT, FAILED_BOOT}, + new byte[] {DEVICE_LOCKED_TRUE, DEVICE_LOCKED_FALSE}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH}, + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {P_224, P_256, P_384, P_521}, + new byte[] {IGNORE_INVALID_TAGS, FAIL_ON_INVALID_TAGS} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) types.length; + while (--index >= 0) { + if (types[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public short length() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + 1)); + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)]; + } + + public void setVal(byte val) { + heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)] = val; + } + + public short getEnumType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE)); + } + + public void setEnumType(short type) { + Util.setShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE), type); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java new file mode 100644 index 0000000..ea73c40 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -0,0 +1,305 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumArrayTag represents ENUM_REP tag type. It has following structure, struct{byte TAG_TYPE; + * short length; struct{short ENUM_ARRAY_TAG; short tagKey; sequence of byte values}} + */ +public class KMEnumArrayTag extends KMTag { + + private static KMEnumArrayTag prototype; + + // The allowed tag keys of enum array type. + private static short[] tags = {PURPOSE, BLOCK_MODE, DIGEST, PADDING, RSA_OAEP_MGF_DIGEST}; + + // Tag Values. + private static Object[] enums = null; + + private KMEnumArrayTag() {} + + private static KMEnumArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumArrayTag(); + } + KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key) { + byte[] vals = getAllowedEnumValues(key); + if (vals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short blobPtr = KMByteBlob.exp(); + return instance(key, blobPtr); + } + + public static short instance(short key, short byteBlob) { + byte[] allowedVals = getAllowedEnumValues(key); + if (allowedVals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + KMByteBlob blob = KMByteBlob.cast(byteBlob); + short byteIndex = 0; + short enumIndex; + boolean validValue; + while (byteIndex < blob.length()) { + enumIndex = 0; + validValue = false; + while (enumIndex < allowedVals.length) { + if (blob.get(byteIndex) == allowedVals[enumIndex]) { + validValue = true; + break; + } + enumIndex++; + } + if (!validValue) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byteIndex++; + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMEnumArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_ARRAY_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // allowed tag values. + enums = + new Object[] { + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {ECB, CBC, CTR, GCM}, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + new byte[] { + PADDING_NONE, RSA_OAEP, RSA_PSS, RSA_PKCS1_1_5_ENCRYPT, RSA_PKCS1_1_5_SIGN, PKCS7 + }, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + }; + } + } + + private static byte[] getAllowedEnumValues(short key) { + create(); + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return (byte[]) enums[index]; + } + } + return null; + } + + public static short getValues(short tagId, short params, byte[] buf, short start) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag == KMType.INVALID_VALUE) { + return KMType.INVALID_VALUE; + } + tag = KMEnumArrayTag.cast(tag).getValues(); + return KMByteBlob.cast(tag).getValues(buf, start); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + while (index < KMEnumArrayTag.cast(tag).length()) { + if (tagValue == KMEnumArrayTag.cast(tag).get(index)) { + return true; + } + index++; + } + } + return false; + } + + public static short length(short tagId, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + return KMEnumArrayTag.cast(tag).length(); + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_ARRAY_TAG; + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } + + public short get(short index) { + return KMByteBlob.cast(getValues()).get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (get(index) == (byte) tagValue) { + return true; + } + index++; + } + return false; + } + + public boolean isValidDigests(byte alg) { + short index = 0; + short digest; + while (index < length()) { + digest = get(index); + switch (alg) { + case KMType.EC: + case KMType.RSA: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256 && digest != KMType.SHA1) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (digest != KMType.DIGEST_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPaddingModes(byte alg) { + short index = 0; + short padding; + while (index < length()) { + padding = get(index); + switch (alg) { + case KMType.RSA: + if (padding != KMType.RSA_OAEP + && padding != KMType.PADDING_NONE + && padding != KMType.RSA_PKCS1_1_5_SIGN + && padding != KMType.RSA_PKCS1_1_5_ENCRYPT + && padding != KMType.RSA_PSS) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (padding != KMType.PKCS7 && padding != KMType.PADDING_NONE) { + return false; + } + break; + case KMType.EC: + case KMType.HMAC: + if (padding != PADDING_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPurpose(byte alg) { + short index = 0; + short purpose; + while (index < length()) { + purpose = get(index); + switch (purpose) { + case KMType.DECRYPT: + case KMType.ENCRYPT: + if (alg != KMType.RSA && alg != KMType.AES && alg != KMType.DES) { + return false; + } + break; + case KMType.SIGN: + case KMType.VERIFY: + if (alg != KMType.HMAC && alg != KMType.RSA && alg != KMType.EC) { + return false; + } + break; + case KMType.WRAP_KEY: + if (alg != KMType.RSA) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidBlockMode(byte alg) { + if (alg == KMType.AES || alg == KMType.DES) { + return true; + } else { + return false; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java new file mode 100644 index 0000000..a7bcbe6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -0,0 +1,152 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumTag represents ENUM Tag type specified in android keymaster hal specifications. struct{byte + * TAG_TYPE; short length; struct{short ENUM_TAG; short tagKey; byte value}} + */ +public class KMEnumTag extends KMTag { + + private static KMEnumTag prototype; + + // The allowed tag keys of type enum tag. + private static short[] tags = { + ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE + }; + + private static Object[] enums = null; + + private KMEnumTag() {} + + private static KMEnumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumTag(); + } + KMType.instanceTable[KM_ENUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateEnum(key, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short) 4); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + return ptr; + } + + public static short instance(short key, byte val) { + if (!validateEnum(key, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = val; + return ptr; + } + + public static KMEnumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // enum tag values. + enums = + new Object[] { + new byte[] {RSA, DES, EC, AES, HMAC}, + new byte[] {P_224, P_256, P_384, P_521, CURVE_25519}, + new byte[] {STANDALONE, REQUIRES_FILE_SYSTEM}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH, ANY}, + new byte[] {GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public static short getValue(short tagKey, short keyParameters) { + short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagKey, keyParameters); + if (tagPtr != KMType.INVALID_VALUE) { + return heap[(short) (tagPtr + TLV_HEADER_SIZE + 4)]; + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_TAG; + } + + public byte getValue() { + return heap[(short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java new file mode 100644 index 0000000..bbae870 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java @@ -0,0 +1,134 @@ +/* + * 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; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short INCOMPATIBLE_PURPOSE = 3; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INCOMPATIBLE_ALGORITHM = 5; + public static final short UNSUPPORTED_KEY_SIZE = 6; + public static final short UNSUPPORTED_BLOCK_MODE = 7; + public static final short INCOMPATIBLE_BLOCK_MODE = 8; + public static final short UNSUPPORTED_MAC_LENGTH = 9; + public static final short UNSUPPORTED_PADDING_MODE = 10; + public static final short INCOMPATIBLE_PADDING_MODE = 11; + public static final short UNSUPPORTED_DIGEST = 12; + public static final short INCOMPATIBLE_DIGEST = 13; + + public static final short UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = 19; + + /** For PKCS8 & PKCS12 */ + public static final short INVALID_INPUT_LENGTH = 21; + + public static final short KEY_USER_NOT_AUTHENTICATED = 26; + public static final short INVALID_OPERATION_HANDLE = 28; + public static final short INSUFFICIENT_BUFFER_SPACE = 29; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_KEY_BLOB = 33; + + public static final short INVALID_ARGUMENT = 38; + public static final short UNSUPPORTED_TAG = 39; + public static final short INVALID_TAG = 40; + public static final short IMPORT_PARAMETER_MISMATCH = 44; + public static final short OPERATION_CANCELLED = 46; + + public static final short MISSING_NONCE = 51; + public static final short INVALID_NONCE = 52; + public static final short MISSING_MAC_LENGTH = 53; + public static final short CALLER_NONCE_PROHIBITED = 55; + public static final short KEY_MAX_OPS_EXCEEDED = 56; + public static final short INVALID_MAC_LENGTH = 57; + public static final short MISSING_MIN_MAC_LENGTH = 58; + public static final short UNSUPPORTED_MIN_MAC_LENGTH = 59; + public static final short UNSUPPORTED_EC_CURVE = 61; + public static final short KEY_REQUIRES_UPGRADE = 62; + + public static final short ATTESTATION_CHALLENGE_MISSING = 63; + public static final short ATTESTATION_APPLICATION_ID_MISSING = 65; + public static final short CANNOT_ATTEST_IDS = 66; + public static final short ROLLBACK_RESISTANCE_UNAVAILABLE = 67; + + public static final short NO_USER_CONFIRMATION = 71; + public static final short DEVICE_LOCKED = 72; + public static final short EARLY_BOOT_ENDED = 73; + public static final short ATTESTATION_KEYS_NOT_PROVISIONED = 74; + public static final short INCOMPATIBLE_MGF_DIGEST = 78; + public static final short UNSUPPORTED_MGF_DIGEST = 79; + public static final short MISSING_NOT_BEFORE = 80; + public static final short MISSING_NOT_AFTER = 81; + public static final short MISSING_ISSUER_SUBJECT_NAME = 82; + public static final short INVALID_ISSUER_SUBJECT_NAME = 83; + + public static final short UNIMPLEMENTED = 100; + public static final short UNKNOWN_ERROR = 1000; + + // Extended errors + public static final short SW_CONDITIONS_NOT_SATISFIED = 10001; + public static final short UNSUPPORTED_CLA = 10002; + public static final short INVALID_P1P2 = 10003; + public static final short UNSUPPORTED_INSTRUCTION = 10004; + public static final short CMD_NOT_ALLOWED = 10005; + public static final short SW_WRONG_LENGTH = 10006; + public static final short INVALID_DATA = 10007; + + // Crypto errors + public static final short CRYPTO_ILLEGAL_USE = 10008; + public static final short CRYPTO_ILLEGAL_VALUE = 10009; + public static final short CRYPTO_INVALID_INIT = 10010; + public static final short CRYPTO_NO_SUCH_ALGORITHM = 10011; + public static final short CRYPTO_UNINITIALIZED_KEY = 10012; + // Generic Unknown error. + public static final short GENERIC_UNKNOWN_ERROR = 10013; + + // Remote key provisioning error codes. + public static final short STATUS_FAILED = 32000; + public static final short STATUS_INVALID_MAC = 32001; + public static final short STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 32002; + public static final short STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 32003; + public static final short STATUS_INVALID_EEK = 32004; + public static final short INVALID_STATE = 32005; + + public static short translate(short err) { + switch (err) { + case SW_CONDITIONS_NOT_SATISFIED: + case UNSUPPORTED_CLA: + case INVALID_P1P2: + case INVALID_DATA: + case CRYPTO_ILLEGAL_USE: + case CRYPTO_ILLEGAL_VALUE: + case CRYPTO_INVALID_INIT: + case CRYPTO_UNINITIALIZED_KEY: + case GENERIC_UNKNOWN_ERROR: + case CMD_NOT_ALLOWED: + case UNKNOWN_ERROR: + return UNKNOWN_ERROR; + case CRYPTO_NO_SUCH_ALGORITHM: + return UNSUPPORTED_ALGORITHM; + case UNSUPPORTED_INSTRUCTION: + case SW_WRONG_LENGTH: + return UNIMPLEMENTED; + } + return err; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java new file mode 100644 index 0000000..e6b1d37 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -0,0 +1,171 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHardwareAuthToken represents HardwareAuthToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HW_AUTH_TOKEN_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMInteger Challenge; KMInteger UserId; KMInteger AuthenticatorId; UserAuthType + * HwAuthenticatorId; KMInteger TimeStamp; KMByteBlob Mac} + */ +public class KMHardwareAuthToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte USER_ID = 0x01; + public static final byte AUTHENTICATOR_ID = 0x02; + public static final byte HW_AUTHENTICATOR_TYPE = 0x03; + public static final byte TIMESTAMP = 0x04; + public static final byte MAC = 0x05; + + private static KMHardwareAuthToken prototype; + + private KMHardwareAuthToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(USER_ID, KMInteger.exp()); + arr.add(AUTHENTICATOR_ID, KMInteger.exp()); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHardwareAuthToken proto(short ptr) { + if (prototype == null) { + prototype = new KMHardwareAuthToken(); + } + KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(USER_ID, KMInteger.uint_16((short) 0)); + arr.add(AUTHENTICATOR_ID, KMInteger.uint_16((short) 0)); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 6) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHardwareAuthToken cast(short ptr) { + if (heap[ptr] != HW_AUTH_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getUserId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(USER_ID); + } + + public void setUserId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(USER_ID, vals); + } + + public short getAuthenticatorId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); + } + + public void setAuthenticatorId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); + } + + public short getHwAuthenticatorType() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); + } + + public void setHwAuthenticatorType(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java new file mode 100644 index 0000000..8642803 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -0,0 +1,110 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHmacSharingParameters represents HmacSharingParameters structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HMAC_SHARING_PARAM_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMByteBlob Seed; KMByteBlob Nonce} + */ +public class KMHmacSharingParameters extends KMType { + + public static final byte SEED = 0x00; + public static final byte NONCE = 0x01; + + private static KMHmacSharingParameters prototype; + + private KMHmacSharingParameters() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SEED, KMByteBlob.exp()); + arr.add(NONCE, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHmacSharingParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMHmacSharingParameters(); + } + KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short) 2); + if (KMArray.cast(vals).length() != 2) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHmacSharingParameters cast(short ptr) { + if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getNonce() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(NONCE); + } + + public void setNonce(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(NONCE, vals); + } + + public short getSeed() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SEED); + } + + public void setSeed(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SEED, vals); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java new file mode 100644 index 0000000..b09de0f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -0,0 +1,215 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8 bit, 16 bit, 32 bit and 64 bit unsigned integer. It corresponds to CBOR uint type. + * struct{byte INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMInteger extends KMType { + + public static final byte UINT_32 = 4; + public static final byte UINT_64 = 8; + private static KMInteger prototype; + + protected KMInteger() {} + + private static KMInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMInteger(); + } + KMType.instanceTable[KM_INTEGER_OFFSET] = ptr; + return prototype; + } + + // | TYPE(1) | LEN(2) | DATA(4 / 8) | + public static short exp() { + return KMType.exp(INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = UINT_64; + } else { + length = UINT_32; + } + return KMType.instance(INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + short ptr = instance(UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + short ptr = instance(UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + short ptr = instance(UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + short ptr = instance(UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64); + return ptr; + } + + public static short compare(short num1, short num2) { + short num1Buf = repository.alloc((short) 8); + short num2Buf = repository.alloc((short) 8); + Util.arrayFillNonAtomic(repository.getHeap(), num1Buf, (short) 8, (byte) 0); + Util.arrayFillNonAtomic(repository.getHeap(), num2Buf, (short) 8, (byte) 0); + short len = KMInteger.cast(num1).length(); + KMInteger.cast(num1).getValue(repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len); + len = KMInteger.cast(num2).length(); + KMInteger.cast(num2).getValue(repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len); + return KMInteger.unsignedByteArrayCompare( + repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, (short) 8); + } + + public static byte unsignedByteArrayCompare( + byte[] a1, short offset1, byte[] a2, short offset2, short length) { + byte count = (byte) 0; + short val1 = (short) 0; + short val2 = (short) 0; + + for (; count < length; count++) { + val1 = (short) (a1[(short) (count + offset1)] & 0x00FF); + val2 = (short) (a2[(short) (count + offset2)] & 0x00FF); + + if (val1 < val2) { + return -1; + } + if (val1 > val2) { + return 1; + } + } + return 0; + } + + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + // Get the start of value + public short getStartOff() { + return (short) (getBaseOffset() + TLV_HEADER_SIZE); + } + + public void getValue(byte[] dest, short destOff, short length) { + if (length < length()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (length > length()) { + length = length(); + destOff += length; + } + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length); + } + + public void setValue(byte[] src, short srcOff) { + Util.arrayCopyNonAtomic(src, srcOff, heap, getStartOff(), length()); + } + + public short value(byte[] dest, short destOff) { + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length()); + return length(); + } + + public short toLittleEndian(byte[] dest, short destOff) { + short index = (short) (length() - 1); + while (index >= 0) { + dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)]; + index--; + } + return length(); + } + + public short getShort() { + return Util.getShort(heap, (short) (getStartOff() + 2)); + } + + public short getSignificantShort() { + return Util.getShort(heap, getStartOff()); + } + + public byte getByte() { + return heap[(short) (getStartOff() + 3)]; + } + + public boolean isZero() { + if (getShort() == 0 && getSignificantShort() == 0) { + return true; + } + return false; + } + + protected short getBaseOffset() { + return instanceTable[KM_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java new file mode 100644 index 0000000..bf45981 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -0,0 +1,163 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerArrayTag represents UINT_REP and ULONG_REP tags specified in keymaster hal specs. + * struct{byte TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG; short tagKey; short arrPtr}, + * where arrPtr is the pointer to KMArray of KMInteger instances. + */ +public class KMIntegerArrayTag extends KMTag { + + private static final short[] tags = {USER_SECURE_ID}; + private static KMIntegerArrayTag prototype; + + private KMIntegerArrayTag() {} + + private static KMIntegerArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerArrayTag(); + } + KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(KMInteger.exp()); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(); + return instance(tagType, key, arrPtr); + } + + public static short instance(short tagType, short key, short arrObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[arrObj] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrObj); + return ptr; + } + + public static KMIntegerArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + tag = KMIntegerArrayTag.cast(tag).getValues(); + while (index < KMArray.cast(tag).length()) { + if (KMInteger.compare(tagValue, KMArray.cast(tag).get(index)) == 0) { + return true; + } + index++; + } + } + return false; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short ptr = getValues(); + return KMArray.cast(ptr).length(); + } + + public void add(short index, short val) { + KMArray arr = KMArray.cast(getValues()); + arr.add(index, val); + } + + public short get(short index) { + KMArray arr = KMArray.cast(getValues()); + return arr.get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (KMInteger.compare(tagValue, get(index)) == 0) { + return true; + } + index++; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java new file mode 100644 index 0000000..d4c4458 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -0,0 +1,218 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerTag represents UINT, ULONG and DATE tags specified in keymaster hal specs. struct{byte + * TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG/DATE_TAG; short tagKey; 4 or 8 byte + * value}} + */ +public class KMIntegerTag extends KMTag { + + // Allowed tag keys. + private static final short[] tags = { + // UINT + KEYSIZE, + MIN_MAC_LENGTH, + MIN_SEC_BETWEEN_OPS, + MAX_USES_PER_BOOT, + USERID, + AUTH_TIMEOUT, + OS_VERSION, + OS_PATCH_LEVEL, + VENDOR_PATCH_LEVEL, + BOOT_PATCH_LEVEL, + MAC_LENGTH, + // ULONG + RSA_PUBLIC_EXPONENT, + // DATE + ACTIVE_DATETIME, + ORIGINATION_EXPIRE_DATETIME, + USAGE_EXPIRE_DATETIME, + CREATION_DATETIME, + CERTIFICATE_NOT_BEFORE, + CERTIFICATE_NOT_AFTER, + USAGE_COUNT_LIMIT, + // custom tag + AUTH_TIMEOUT_MILLIS, + }; + private static KMIntegerTag prototype; + + private KMIntegerTag() {} + + private static KMIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerTag(); + } + KMType.instanceTable[KM_INTEGER_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + return instance(tagType, key, intPtr); + } + + public static short instance(short tagType, short key, short intObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[intObj] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intObj); + return ptr; + } + + public static KMIntegerTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == DATE_TAG) || (tagType == UINT_TAG) || (tagType == ULONG_TAG); + } + + public static short getShortValue(short tagType, short tagKey, short keyParameters) { + short ptr; + if (tagType == UINT_TAG) { + ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + if (KMInteger.cast(ptr).getSignificantShort() == 0) { + return KMInteger.cast(ptr).getShort(); + } + } + } + return KMType.INVALID_VALUE; + } + + public static short getValue( + byte[] buf, short offset, short tagType, short tagKey, short keyParameters) { + short ptr; + if ((tagType == UINT_TAG) || (tagType == ULONG_TAG) || (tagType == DATE_TAG)) { + ptr = KMKeyParameters.findTag(tagType, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + return KMInteger.cast(ptr).value(buf, offset); + } + } + return KMType.INVALID_VALUE; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + KMInteger obj = KMInteger.cast(getValue()); + return obj.length(); + } + + public boolean isValidKeySize(byte alg) { + short val = KMIntegerTag.cast(KMType.instanceTable[KM_INTEGER_TAG_OFFSET]).getValue(); + if (KMInteger.cast(val).getSignificantShort() != 0) { + return false; + } + val = KMInteger.cast(val).getShort(); + switch (alg) { + case KMType.RSA: + if (val == 2048) { + return true; + } + break; + case KMType.AES: + if (val == 128 || val == 256) { + return true; + } + break; + case KMType.DES: + if (val == 168) { + return true; + } + break; + case KMType.EC: + if (val == 256) { + return true; + } + break; + case KMType.HMAC: + if (val % 8 == 0 && val >= 64 && val <= 512) { + return true; + } + break; + default: + break; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java new file mode 100644 index 0000000..37b8b7f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -0,0 +1,124 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyCharacteristics represents KeyCharacteristics structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte KEY_CHAR_TYPE; short length=3; + * short arrayPtr} where arrayPtr is a pointer to ordered array with 1 or 3 following elements: + * {KMKeyParameters sb; KMKeyParameters tee; KMKeyParameters keystore} + */ +public class KMKeyCharacteristics extends KMType { + + public static final byte STRONGBOX_ENFORCED = 0x00; + public static final byte TEE_ENFORCED = 0x01; + public static final byte KEYSTORE_ENFORCED = 0x02; + private static KMKeyCharacteristics prototype; + + private KMKeyCharacteristics() {} + + public static short exp() { + short keyParamExp = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short) 3); + + KMArray arr = KMArray.cast(arrPtr); + arr.add(STRONGBOX_ENFORCED, keyParamExp); + arr.add(TEE_ENFORCED, keyParamExp); + arr.add(KEYSTORE_ENFORCED, keyParamExp); + return instance(arrPtr); + } + + private static KMKeyCharacteristics proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyCharacteristics(); + } + KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_CHAR_TYPE, (short) 3); + if (KMArray.cast(vals).length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyCharacteristics cast(short ptr) { + if (heap[ptr] != KEY_CHAR_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getKeystoreEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(KEYSTORE_ENFORCED); + } + + public void setKeystoreEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(KEYSTORE_ENFORCED, ptr); + } + + public short getTeeEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TEE_ENFORCED); + } + + public void setTeeEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TEE_ENFORCED, ptr); + } + + public short getStrongboxEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(STRONGBOX_ENFORCED); + } + + public void setStrongboxEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(STRONGBOX_ENFORCED, ptr); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java new file mode 100644 index 0000000..54ab6ee --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -0,0 +1,472 @@ +/* + * 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.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyParameters represents KeyParameters structure from android keymaster hal specifications. It + * corresponds to CBOR map type. struct{byte KEY_PARAM_TYPE; short length=2; short arrayPtr} where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMKeyParameters extends KMType { + + private static final short[] customTags = { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + private static final short[] tagArr = { + // Unsupported tags. + KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS + }; + private static final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + }; + private static final short[] swEnforcedTagsArr = { + KMType.DATE_TAG, KMType.ACTIVE_DATETIME, + KMType.DATE_TAG, KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, + KMType.UINT_TAG, KMType.USERID, + KMType.DATE_TAG, KMType.CREATION_DATETIME, + KMType.UINT_TAG, KMType.USAGE_COUNT_LIMIT, + KMType.BOOL_TAG, KMType.ALLOW_WHILE_ON_BODY, + KMType.UINT_TAG, KMType.MAX_BOOT_LEVEL, + }; + private static final short[] teeEnforcedTagsArr = { + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + }; + private static final short[] invalidTagsArr = { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + }; + private static KMKeyParameters prototype; + + private KMKeyParameters() {} + + private static KMKeyParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyParameters(); + } + KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.FAIL_ON_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short expAny() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.IGNORE_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_PARAM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyParameters cast(short ptr) { + if (heap[ptr] != KEY_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short findTag(short tagType, short tagKey, short keyParam) { + KMKeyParameters instParam = KMKeyParameters.cast(keyParam); + return instParam.findTag(tagType, tagKey); + } + + public static boolean hasUnsupportedTags(short keyParamsPtr) { + byte index = 0; + short tagInd; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + while (tagInd < (short) tagArr.length) { + if ((tagArr[tagInd] == tagType) && (tagArr[(short) (tagInd + 1)] == tagKey)) { + return true; + } + tagInd += 2; + } + index++; + } + return false; + } + + // KDF, ECIES_SINGLE_HASH_MODE missing from types.hal + public static short makeSbEnforced( + short keyParamsPtr, + byte origin, + short osVersionObjPtr, + short osPatchObjPtr, + short vendorPatchObjPtr, + short bootPatchObjPtr, + byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) hwEnforcedTagArr.length) { + if ((hwEnforcedTagArr[tagInd] == tagType) + && (hwEnforcedTagArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + short originTag = KMEnumTag.instance(KMType.ORIGIN, origin); + Util.setShort(scratchPad, arrInd, originTag); + arrInd += 2; + short osVersionTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION, osVersionObjPtr); + Util.setShort(scratchPad, arrInd, osVersionTag); + arrInd += 2; + short osPatchTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, osPatchObjPtr); + Util.setShort(scratchPad, arrInd, osPatchTag); + arrInd += 2; + short vendorPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, vendorPatchObjPtr); + Util.setShort(scratchPad, arrInd, vendorPatchTag); + arrInd += 2; + short bootPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, bootPatchObjPtr); + Util.setShort(scratchPad, arrInd, bootPatchTag); + arrInd += 2; + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHwEnforced(short sb, short tee) { + short len = KMKeyParameters.cast(sb).length(); + len += KMKeyParameters.cast(tee).length(); + short hwEnf = KMArray.instance(len); + sb = KMKeyParameters.cast(sb).getVals(); + tee = KMKeyParameters.cast(tee).getVals(); + len = KMArray.cast(sb).length(); + short src = 0; + short dest = 0; + short val = 0; + while (src < len) { + val = KMArray.cast(sb).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + src = 0; + len = KMArray.cast(tee).length(); + while (src < len) { + val = KMArray.cast(tee).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + return KMKeyParameters.instance(hwEnf); + } + + // ALL_USERS, EXPORTABLE missing from types.hal + public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) swEnforcedTagsArr.length) { + if ((swEnforcedTagsArr[tagInd] == tagType) + && (swEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) teeEnforcedTagsArr.length) { + if ((teeEnforcedTagsArr[tagInd] == tagType) + && (teeEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + appId = KMTag.INVALID_VALUE; + } + } + short appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + appData = KMTag.INVALID_VALUE; + } + } + return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); + } + + public static short makeHidden( + short appIdBlob, short appDataBlob, short rootOfTrustBlob, byte[] scratchPad) { + // Order in which the hidden array is created should not change. + short index = 0; + KMByteBlob.cast(rootOfTrustBlob); + Util.setShort(scratchPad, index, rootOfTrustBlob); + index += 2; + if (appIdBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appIdBlob); + Util.setShort(scratchPad, index, appIdBlob); + index += 2; + } + if (appDataBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appDataBlob); + Util.setShort(scratchPad, index, appDataBlob); + index += 2; + } + return createKeyParameters(scratchPad, (short) (index / 2)); + } + + public static boolean isValidTag(short tagType, short tagKey) { + short index = 0; + if (tagKey == KMType.INVALID_TAG) { + return false; + } + while (index < invalidTagsArr.length) { + if ((tagType == invalidTagsArr[index]) && (tagKey == invalidTagsArr[(short) (index + 1)])) { + return false; + } + index += 2; + } + return true; + } + + public static short createKeyParameters(byte[] ptrArr, short len) { + short arrPtr = KMArray.instance(len); + short index = 0; + short ptr = 0; + while (index < len) { + KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + index++; + ptr += 2; + } + return KMKeyParameters.instance(arrPtr); + } + + public static short makeCustomTags(short keyParams, byte[] scratchPad) { + short index = 0; + short tagPtr; + short offset = 0; + short len = (short) customTags.length; + short tagType; + while (index < len) { + tagType = customTags[(short) (index + 1)]; + switch (tagType) { + case KMType.AUTH_TIMEOUT_MILLIS: + short authTimeOutTag = + KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + if (authTimeOutTag != KMType.INVALID_VALUE) { + tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); + Util.setShort(scratchPad, offset, tagPtr); + offset += 2; + } + break; + default: + break; + } + index += 2; + } + return createKeyParameters(scratchPad, (short) (offset / 2)); + } + + public static short createAuthTimeOutMillisTag( + short authTimeOutTag, byte[] scratchPad, short offset) { + short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (offset + 8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, offset, (short) (offset + 8), (short) (offset + 16)); + return KMIntegerTag.instance( + KMType.ULONG_TAG, + KMType.AUTH_TIMEOUT_MILLIS, + KMInteger.uint_64(scratchPad, (short) (offset + 8))); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short findTag(short tagType, short tagKey) { + KMArray vals = KMArray.cast(getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short ret = KMType.INVALID_VALUE; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + if ((tagKey == key) && (tagType == type)) { + ret = obj; + break; + } + index++; + } + return ret; + } + + public void deleteCustomTags() { + short arrPtr = getVals(); + short index = (short) (customTags.length - 1); + short obj; + while (index >= 0) { + obj = findTag(customTags[(short) (index - 1)], customTags[index]); + if (obj != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).deleteLastEntry(); + } + index -= 2; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java new file mode 100644 index 0000000..d8bc21d --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -0,0 +1,5172 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMOperation; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.APDU; +import javacard.framework.Applet; +import javacard.framework.AppletEvent; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacardx.apdu.ExtendedLength; + +/** + * KMKeymasterApplet implements the javacard applet. It creates an instance of the KMRepository and + * other install time objects. It also implements the keymaster state machine and handles javacard + * applet life cycle events. + */ +public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength { + + // Constants. + // Represents RSA_PUBLIC_EXPONENT value 65537. + public static final byte[] F4 = {0x01, 0x00, 0x01}; + // Block size of AES algorithm. + public static final byte AES_BLOCK_SIZE = 16; + // Block size of DES algorithm. + public static final byte DES_BLOCK_SIZE = 8; + // The Key size in bits for the master key. + public static final short MASTER_KEY_SIZE = 128; + // The Key size of the transport key used in importWrappedKey. + public static final byte WRAPPING_KEY_SIZE = 32; + // The maximum allowed simultaneous operations. + public static final byte MAX_OPERATIONS_COUNT = 4; + // The size of the verified boot key in ROT. + public static final byte VERIFIED_BOOT_KEY_SIZE = 32; + // The size of the verified boot hash in ROT. + public static final byte VERIFIED_BOOT_HASH_SIZE = 32; + // The security level of TEE. + public static final byte TRUSTED_ENVIRONMENT = 1; + // "Keymaster HMAC Verification" - used for HMAC key verification. + public static final byte[] sharingCheck = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x48, 0x4D, 0x41, 0x43, 0x20, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E + }; + // The ckdfLabel "KeymasterSharedMac" in hex. + public static final byte[] ckdfLabel = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, + 0x61, 0x63 + }; + // The "Auth Verification" string in hex. + public static final byte[] authVerification = { + 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, + 0x6E + }; + // The "confirmation token" string in hex. + public static final byte[] confirmationToken = { + 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B, + 0x65, 0x6E + }; + // The maximum buffer size for the encoded COSE structures. + public static final short MAX_COSE_BUF_SIZE = (short) 512; + // Maximum allowed buffer size for to encode the key parameters + // which is used while creating mac for key parameters. + public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K + // Temporary variables array size to store intermediary results. + public static final byte TMP_VARIABLE_ARRAY_SIZE = 5; + // Data Dictionary items + // Maximum Dictionary size. + public static final byte DATA_ARRAY_SIZE = 39; + // Below are the offsets of the data dictionary items. + public static final byte KEY_PARAMETERS = 0; + public static final byte KEY_CHARACTERISTICS = 1; + public static final byte HIDDEN_PARAMETERS = 2; + public static final byte HW_PARAMETERS = 3; + public static final byte SW_PARAMETERS = 4; + public static final byte AUTH_DATA = 5; + public static final byte AUTH_TAG = 6; + public static final byte NONCE = 7; + public static final byte KEY_BLOB = 8; + public static final byte AUTH_DATA_LENGTH = 9; + public static final byte SECRET = 10; + public static final byte ROT = 11; + public static final byte DERIVED_KEY = 12; + public static final byte RSA_PUB_EXPONENT = 13; + public static final byte APP_ID = 14; + public static final byte APP_DATA = 15; + public static final byte PUB_KEY = 16; + public static final byte IMPORTED_KEY_BLOB = 17; + public static final byte ORIGIN = 18; + public static final byte NOT_USED = 19; + public static final byte MASKING_KEY = 20; + public static final byte HMAC_SHARING_PARAMS = 21; + public static final byte OP_HANDLE = 22; + public static final byte IV = 23; + public static final byte INPUT_DATA = 24; + public static final byte OUTPUT_DATA = 25; + public static final byte HW_TOKEN = 26; + public static final byte VERIFICATION_TOKEN = 27; + public static final byte SIGNATURE = 28; + public static final byte ATTEST_KEY_BLOB = 29; + public static final byte ATTEST_KEY_PARAMS = 30; + public static final byte ATTEST_KEY_ISSUER = 31; + public static final byte CERTIFICATE = 32; + public static final byte PLAIN_SECRET = 33; + public static final byte TEE_PARAMETERS = 34; + public static final byte SB_PARAMETERS = 35; + public static final byte CONFIRMATION_TOKEN = 36; + public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37; + public static final byte CUSTOM_TAGS = 38; + // Below are the Keyblob offsets. + public static final byte KEY_BLOB_VERSION_OFFSET = 0; + public static final byte KEY_BLOB_SECRET = 1; + public static final byte KEY_BLOB_NONCE = 2; + public static final byte KEY_BLOB_AUTH_TAG = 3; + public static final byte KEY_BLOB_PARAMS = 4; + public static final byte KEY_BLOB_CUSTOM_TAGS = 5; + public static final byte KEY_BLOB_PUB_KEY = 6; + // AES GCM Auth tag length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_AUTH_TAG_LENGTH = 16; + // AES GCM nonce length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // KEYBLOB_CURRENT_VERSION goes into KeyBlob and will affect all + // the KeyBlobs if it is changed. please increment this + // version number whenever you change anything related to + // KeyBlob (structure, encryption algorithm etc). + public static final byte KEYBLOB_CURRENT_VERSION = 3; + // KeyBlob Verion 1 constant. + public static final byte KEYBLOB_VERSION_1 = 1; + // Array sizes of KeyBlob under different versions. + // The array size of a Symmetric key's KeyBlob for Version2 and Version3 + public static final byte SYM_KEY_BLOB_SIZE_V2_V3 = 6; + // The array size of a Asymmetric key's KeyBlob for Version2 and Version3 + public static final byte ASYM_KEY_BLOB_SIZE_V2_V3 = 7; + // The array size of a Symmetric key's KeyBlob for Version1 + public static final byte SYM_KEY_BLOB_SIZE_V1 = 5; + // The array size of a Asymmetric key's KeyBlob for Version1 + public static final byte ASYM_KEY_BLOB_SIZE_V1 = 6; + // The array size of a Symmetric key's KeyBlob for Version0 + public static final byte SYM_KEY_BLOB_SIZE_V0 = 4; + // The array size of a Asymmetric key's KeyBlob for Version0 + public static final byte ASYM_KEY_BLOB_SIZE_V0 = 5; + // Key type constants + // Represents the type of the Symmetric key. + public static final byte SYM_KEY_TYPE = 0; + // Represents the type of the Asymmetric key. + public static final byte ASYM_KEY_TYPE = 1; + // SHA-256 Digest length in bits + public static final short SHA256_DIGEST_LEN_BITS = 256; + // Minimum HMAC length in bits + public static final byte MIN_HMAC_LENGTH_BITS = 64; + // Below are the constants for provision reporting status + public static final short NOT_PROVISIONED = 0x0000; + public static final short PROVISION_STATUS_ATTESTATION_KEY = 0x0001; + public static final short PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x0002; + public static final short PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x0004; + public static final short PROVISION_STATUS_ATTEST_IDS = 0x0008; + public static final short PROVISION_STATUS_PRESHARED_SECRET = 0x0010; + public static final short PROVISION_STATUS_PROVISIONING_LOCKED = 0x0020; + public static final short PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR = 0x0040; + public static final short PROVISION_STATUS_UDS_CERT_CHAIN = 0x0080; + public static final short PROVISION_STATUS_SE_LOCKED = 0x0100; + public static final short PROVISION_STATUS_OEM_PUBLIC_KEY = 0x0200; + public static final short PROVISION_STATUS_SECURE_BOOT_MODE = 0x0400; + // This is the P1P2 constant of the APDU command header. + protected static final short KM_HAL_VERSION = (short) 0x6000; + // OEM lock / unlock verification constants. + // This is the verification label to authenticate the OEM to lock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_LOCK_PROVISION_VERIFICATION_LABEL = { // "OEM Provisioning Lock" + 0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x4c, 0x6f, 0x63, 0x6b + }; + // This is the verification label to authenticate the OEM to unlock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + // The maximum size of the seed allowed for the RNG entropy + protected static final short MAX_SEED_SIZE = 2048; + // The maximum size of the certificate returned by the generate key command. + protected static final short MAX_CERT_SIZE = 3000; + // The maximum size of the encoded key characteristics in CBOR. + protected static final short MAX_KEY_CHARS_SIZE = 512; + // The maximum size of the serialized KeyBlob. + protected static final short MAX_KEYBLOB_SIZE = 1024; + // The maximum size of the Auth data which is used while encrypting/decrypting the KeyBlob. + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + // The minimum bits in length for AES-GCM tag. + private static final byte MIN_GCM_TAG_LENGTH_BITS = (short) 96; + // The maximum bits in length for AES-GCM tag. + private static final short MAX_GCM_TAG_LENGTH_BITS = (short) 128; + // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys + private static final byte[] defaultSubject = { + 0x30, 0x1F, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x14, 0x41, 0x6e, 0x64, + 0x72, 0x6f, 0x69, 0x64, 0x20, 0x4B, 0x65, 0x79, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x4B, 0x65, + 0x79 + }; + // Constant for Dec 31, 9999 in milliseconds in hex. + private static final byte[] dec319999Ms = { + (byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18 + }; + // Dec 31, 9999 represented in Generalized time format YYYYMMDDhhmmssZ. + // "99991231235959Z" in hex. Refer RFC 5280 section 4.1.2.5.2 + private static final byte[] dec319999 = { + 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, + }; + // Jan 01, 1970 represented in UTC time format YYMMDDhhmmssZ. + // "700101000000Z" in hex. Refer RFC 5280 section 4.1.2.5.1 + private static final byte[] jan01970 = { + 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + }; + // The KeyMint name "JavacardKeymintDevice" returned from getHwInfo. + private static final byte[] JavacardKeymintDevice = { + 0x4a, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, + }; + // The KeyMint author name "Google" returned from getHwInfo. + public static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // Attestation ID tags to be included in attestation record. + private static final short[] attTags = { + KMType.ATTESTATION_ID_BRAND, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_SERIAL + }; + // Below are the constants of instructions in APDU command header. + // Top 32 commands are reserved for provisioning. + private static final byte KEYMINT_CMD_APDU_START = 0x20; + // RKP + public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; // 0x3B + public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; // 0x3C + public static final byte INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29; // 0x3D + public static final byte INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30; // 0x3E + // Constant + public static final byte INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31; // 0x3F + public static final byte INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32; // 0x40 + public static final byte INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33; // 0x41 + public static final byte INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34; // 0x42 + public static final byte INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35; // 0x43 + public static final byte INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36; // 0x44 + private static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; // 0x21 + private static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; // 0x22 + private static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; // 0x23 + private static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; // 0x24 + private static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; // 0x25 + private static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; // 0x26 + private static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; // 0x27 + private static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; // 0x28 + private static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; // 0x29 + private static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; // 0x2A + private static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; // 0x2B + private static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; // 0x2C + private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; // 0x2D + private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; // 0x2E + private static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; // 0x2F + private static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; // 0x30 + private static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; // 0x31 + private static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; // 0x32 + private static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; // 0x33 + private static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20; // 0x34 + private static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; // 0x35 + private static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; // 0x36 + private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; // 0x37 + private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; // 0x38 + private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; // 0x39 + private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; // 0x3A + // The instructions from 0x43 to 0x4C will be reserved for KeyMint 1.0 for any future use. + // KeyMint 2.0 Instructions + private static final byte INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45; // 0x4D + private static final byte INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46; // 0x4E + private static final byte INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47; // 0x4F + private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 48; // 0x50 + private static final byte INS_END_KM_CMD = 0x7F; + // Instruction values from 0xCD to 0xFF are completely reserved for Vendors to use and + // will never be used by the base line code in future. + private static final byte INS_KM_VENDOR_START_CMD = (byte) 0xCD; + private static final byte INS_KM_VENDOR_END_CMD = (byte) 0xFF; + // Index in apduFlagsStatus[] to check if instruction command is case 4 type in the Apdu + protected static final byte APDU_CASE4_COMMAND_STATUS_INDEX = 0; + // Index in apduFlagsStatus[] to check if Apdu setIncomingAndReceive function is called + protected static final byte APDU_INCOMING_AND_RECEIVE_STATUS_INDEX = 1; + // The maximum buffer size of combined seed and nonce. + private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; + // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands. + protected static KMRemotelyProvisionedComponentDevice rkp; + // Instance of Cbor encoder. + protected static KMEncoder encoder; + // Instance of Cbor decoder. + protected static KMDecoder decoder; + // Instance of KMRepository class for memory management. + protected static KMRepository repository; + // Instance of KMSEProvider for doing crypto operations. + protected static KMSEProvider seProvider; + // Holds the instance of KMOperationStates. A maximum of 4 instances of KMOperatioState is + // allowed. + protected static KMOperationState[] opTable; + // Instance of KMKeymintDataStore which helps to store and retrieve the data. + protected static KMKeymintDataStore kmDataStore; + + // Short array used to store the temporary results. + protected static short[] tmpVariables; + // Short array used to hold the dictionary items. + protected static short[] data; + // Buffer to store the transportKey which is used in the import wrapped key. Import wrapped + // key is divided into two stages 1. BEGIN_IMPORT_WRAPPED_KEY 2. FINISH_IMPORT_WRAPPED_KEY. + // The transportKey is retrieved and stored in this buffer at stage 1) and is later used in + // stage 2). + protected static byte[] wrappingKey; + // Transient byte array used to store the flags if APDU command type is of case 4 and if + // APDU setIncomingAndReceive() function is called or not. + protected static byte[] apduStatusFlags; + + /** Registers this applet. */ + protected KMKeymasterApplet(KMSEProvider seImpl) { + seProvider = seImpl; + boolean isUpgrading = seProvider.isUpgrading(); + repository = new KMRepository(isUpgrading); + encoder = new KMEncoder(); + decoder = new KMDecoder(); + kmDataStore = new KMKeymintDataStore(seProvider, repository); + data = JCSystem.makeTransientShortArray(DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + tmpVariables = + JCSystem.makeTransientShortArray(TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + wrappingKey = + JCSystem.makeTransientByteArray((short) (WRAPPING_KEY_SIZE + 1), JCSystem.CLEAR_ON_RESET); + resetWrappingKey(); + apduStatusFlags = JCSystem.makeTransientByteArray((short) 2, JCSystem.CLEAR_ON_RESET); + opTable = new KMOperationState[MAX_OPERATIONS_COUNT]; + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index] = new KMOperationState(); + index++; + } + KMType.initialize(); + if (!isUpgrading) { + // For keyMint 3.0 and above installation, set ignore second Imei flag to false. + kmDataStore.ignoreSecondImei = false; + kmDataStore.createMasterKey(MASTER_KEY_SIZE); + } + // initialize default values + initHmacNonceAndSeed(); + rkp = + new KMRemotelyProvisionedComponentDevice( + encoder, decoder, repository, seProvider, kmDataStore); + } + + /** Sends a response, may be extended response, as requested by the command. */ + public static void sendOutgoing(APDU apdu, short resp) { + // TODO handle the extended buffer stuff. We can reuse this. + short bufferStartOffset = repository.allocAvailableMemory(); + byte[] buffer = repository.getHeap(); + // TODO we can change the following to incremental send. + short bufferLength = + encoder.encode(resp, buffer, bufferStartOffset, repository.getHeapReclaimIndex()); + if (((short) (bufferLength + bufferStartOffset)) > ((short) repository.getHeap().length)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + /** Receives data, which can be extended data, as requested by the command instance. */ + public static short receiveIncoming(APDU apdu, short reqExp) { + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + // TODO add logic to handle the extended length buffer. In this case the memory can be reused + // from extended buffer. + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); + repository.reclaimMemory(bufferLength); + return req; + } + + private static short createKeyBlobInstance(byte keyType) { + short arrayLen = 0; + switch (keyType) { + case ASYM_KEY_TYPE: + arrayLen = ASYM_KEY_BLOB_SIZE_V2_V3; + break; + case SYM_KEY_TYPE: + arrayLen = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return KMArray.instance(arrayLen); + } + + private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { + short index = 0; + short arr = KMKeyParameters.cast(params).getVals(); + short len = KMArray.cast(arr).length(); + short tag; + while (index < len) { + tag = KMArray.cast(arr).get(index); + cert.extensionTag(tag, hwEnforced); + index++; + } + } + + private static void setUniqueId(KMAttestationCert cert, short attAppId, byte[] scratchPad) { + if (!KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)) { + return; + } + // temporal count T + short time = + KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[KEY_PARAMETERS]); + if (time == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + time = KMIntegerTag.cast(time).getValue(); + + // Reset After Rotation R - it will be part of HW Enforced key + // characteristics + byte resetAfterRotation = 0; + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION)) { + resetAfterRotation = 0x01; + } + + cert.makeUniqueId( + scratchPad, + (short) 0, + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + KMInteger.cast(time).length(), + KMByteBlob.cast(attAppId).getBuffer(), + KMByteBlob.cast(attAppId).getStartOff(), + KMByteBlob.cast(attAppId).length(), + resetAfterRotation, + kmDataStore.getMasterKey()); + } + + private static void validateRSAKey(byte[] scratchPad) { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (!KMTag.isValidPublicExponent(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + // Generate key handlers + private static void generateRSAKey(byte[] scratchPad) { + // Validate RSA Key + validateRSAKey(scratchPad); + // Now generate 2048 bit RSA keypair for the given exponent + short[] lengths = tmpVariables; + data[PUB_KEY] = KMByteBlob.instance((short) 256); + data[SECRET] = KMByteBlob.instance((short) 256); + seProvider.createAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + lengths); + + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateAESKey() { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Block mode - array of byte values + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE)) { + short blockModes = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + // If it is a GCM mode + if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)) { + // Min mac length must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short macLength = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + macLength = KMIntegerTag.cast(macLength).getValue(); + // Validate the MIN_MAC_LENGTH for AES - should be multiple of 8, less then 128 bits + // and greater the 96 bits + if (KMInteger.cast(macLength).getSignificantShort() != 0 + || KMInteger.cast(macLength).getShort() > 128 + || KMInteger.cast(macLength).getShort() < 96 + || (KMInteger.cast(macLength).getShort() % 8) != 0) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + } + } + } + + private static void generateAESKey(byte[] scratchPad) { + validateAESKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short len = seProvider.createSymmetricKey(KMType.AES, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateECKeys() { + // Read key size + short ecCurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + /* In KeyMint 2.0, If EC_CURVE not provided, generateKey + * must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE. + */ + if (ecCurve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + short ecKeySize = KMEnumTag.getValue(KMType.KEYSIZE, data[KEY_PARAMETERS]); + if ((ecKeySize != KMType.INVALID_VALUE) && !KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateECKeys(byte[] scratchPad) { + validateECKeys(); + short[] lengths = tmpVariables; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateTDESKey() { + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + } + + private static void generateTDESKey(byte[] scratchPad) { + validateTDESKey(); + short len = seProvider.createSymmetricKey(KMType.DES, (short) 168, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateHmacKey() { + // If params does not contain any digest throw unsupported digest error. + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.UNSUPPORTED_DIGEST); + + // check whether digest sizes are greater then or equal to min mac length. + // Only SHA256 digest must be supported. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + // Read Minimum Mac length + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short minMacLength = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + + if (((short) (minMacLength % 8) != 0) + || minMacLength < MIN_HMAC_LENGTH_BITS + || minMacLength > SHA256_DIGEST_LEN_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + // Read Keysize + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateHmacKey(byte[] scratchPad) { + validateHmacKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + // generate HMAC Key + short len = seProvider.createSymmetricKey(KMType.HMAC, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + // This function is only called from processUpgradeKey command. + // 1. Update the latest values of OSVersion, OSPatch, VendorPatch and BootPatch in the + // KeyBlob's KeyCharacteristics. + // 2. Re-create KeyBlob's KeyCharacteristics from HW_PARAMS to make sure we don't miss + // anything which happens in these functions makeSbEnforced and makeTeeEnforced in + // the future. Like validations. + // 3. No need to create Keystore Enforced list here as it is not required to be included in + // the KeyBlob's KeyCharacteristics. + // 4. No need to create KeyCharacteristics as upgradeKey does not require to return any + // KeyCharacteristics back. + private static void upgradeKeyBlobKeyCharacteristics(short hwParams, byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + hwParams, (byte) data[ORIGIN], osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(hwParams, scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + } + + private static void makeKeyCharacteristics(byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + data[KEY_PARAMETERS], + (byte) data[ORIGIN], + osVersion, + osPatch, + vendorPatch, + bootPatch, + scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(data[KEY_PARAMETERS], scratchPad); + data[SW_PARAMETERS] = KMKeyParameters.makeKeystoreEnforced(data[KEY_PARAMETERS], scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setKeystoreEnforced(data[SW_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]); + } + + private static void createEncryptedKeyBlob(byte[] scratchPad) { + // make root of trust blob + data[ROT] = readROT(scratchPad, KEYBLOB_CURRENT_VERSION); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make hidden key params list + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad); + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_16(KEYBLOB_CURRENT_VERSION); + // create custom tags + data[CUSTOM_TAGS] = KMKeyParameters.makeCustomTags(data[HW_PARAMETERS], scratchPad); + // encrypt the secret and cryptographically attach that to authorization data + encryptSecret(scratchPad); + // create key blob array + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_NONCE, data[NONCE]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_VERSION_OFFSET, data[KEY_BLOB_VERSION_DATA_OFFSET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_CUSTOM_TAGS, data[CUSTOM_TAGS]); + + short tempChar = KMKeyCharacteristics.instance(); + short emptyParam = KMArray.instance((short) 0); + emptyParam = KMKeyParameters.instance(emptyParam); + KMKeyCharacteristics.cast(tempChar).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam); + KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar); + } + + // Read RoT + public static short readROT(byte[] scratchPad, short version) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + // As per IKeyMintDevice.aidl specification The root of trust + // consists of verifyBootKey, boot state and device locked. + if (version <= KEYBLOB_VERSION_1) { + // To parse old keyblobs verified boot hash is included in + // the root of trust. + len += kmDataStore.getVerifiedBootHash(scratchPad, (short) len); + } + short bootState = kmDataStore.getBootState(); + len = Util.setShort(scratchPad, len, bootState); + if (kmDataStore.isDeviceBootLocked()) { + scratchPad[len] = (byte) 1; + } else { + scratchPad[len] = (byte) 0; + } + len++; + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static void encryptSecret(byte[] scratchPad) { + // make nonce + data[NONCE] = KMByteBlob.instance(AES_GCM_NONCE_LENGTH); + data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); + seProvider.newRandomNumber( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length()); + // derive master key - stored in derivedKey + short len = deriveKey(scratchPad); + len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + null, + (short) 0, + (short) 0, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + + if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static byte getKeyType(short hardwareParams) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hardwareParams); + if (KMEnumTag.cast(alg).getValue() == KMType.RSA + || KMEnumTag.cast(alg).getValue() == KMType.EC) { + return ASYM_KEY_TYPE; + } + return SYM_KEY_TYPE; + } + + private static void makeAuthData(short version, byte[] scratchPad) { + // For KeyBlob V2: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + // For KeyBlob V1: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, VERSION and PUB_KEY. + // For KeyBlob V0: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS and PUB_KEY. + // VERSION is included only for KeyBlobs having version >= 1. + // PUB_KEY is included for only ASYMMETRIC KeyBlobs. + short index = 0; + short numParams = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + switch (version) { + case (short) 0: + numParams = 2; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 3; + Util.setShort(scratchPad, (short) 4, data[PUB_KEY]); + } + break; + case (short) 1: + numParams = 3; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 4; + Util.setShort(scratchPad, (short) 6, data[PUB_KEY]); + } + break; + case (short) 2: + numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + index = 0; + short len = 0; + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32), prevReclaimIndex); + Util.arrayCopyNonAtomic( + repository.getHeap(), + authIndex, + repository.getHeap(), + (short) (authIndex + len + 32), + (short) 32); + len = + seProvider.messageDigest256( + repository.getHeap(), + (short) (authIndex + 32), + (short) (len + 32), + repository.getHeap(), + authIndex); + if (len != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index++; + } + short authDataIndex = repository.alloc(len); + Util.arrayCopyNonAtomic( + repository.getHeap(), authIndex, repository.getHeap(), authDataIndex, len); + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + data[AUTH_DATA] = authDataIndex; + data[AUTH_DATA_LENGTH] = len; + } + + private static short deriveKeyForOldKeyBlobs(byte[] scratchPad) { + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + short len = + seProvider.hmacKDF( + kmDataStore.getMasterKey(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + scratchPad, + (short) 0); + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + private static short deriveKey(byte[] scratchPad) { + // For KeyBlob V3: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + short index = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. CUSTOM_TAGS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + short numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + short len = 0; + KMOperation operation = null; + try { + operation = + seProvider.initSymmetricOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) KMType.INVALID_VALUE, + (Object) kmDataStore.getMasterKey(), + KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, + (byte[]) null, + (short) 0, + (short) 0, + (short) 0, + false); + + byte arrayHeader = (byte) 0x80; + arrayHeader |= (byte) numParams; + ((byte[]) repository.getHeap())[authIndex] = arrayHeader; + operation.update(repository.getHeap(), authIndex, (short) 1); + + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) authIndex, prevReclaimIndex); + operation.update(repository.getHeap(), authIndex, len); + index++; + } + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + len = operation.sign(scratchPad, (short) 0, (short) 0, scratchPad, (short) 0); + } finally { + if (operation != null) { + operation.abort(); + } + } + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + public static void sendResponse(APDU apdu, short err) { + short resp = KMArray.instance((short) 1); + err = KMError.translate(err); + short error = KMInteger.uint_16(err); + KMArray.cast(resp).add((short) 0, error); + sendOutgoing(apdu, resp); + } + + public static void generateRkpKey(byte[] scratchPad, short keyParams) { + data[KEY_PARAMETERS] = keyParams; + generateECKeys(scratchPad); + // create key blob + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } + + public static short getPubKey() { + return data[PUB_KEY]; + } + + public static short getPivateKey() { + return data[KEY_BLOB]; + } + + /** + * Encodes the object to the provided apdu buffer. + * + * @param object Object to be encoded. + * @param apduBuf Buffer on which the encoded data is copied. + * @param apduOff Start offset of the buffer. + * @param maxLen Max value of the expected out length. + * @return length of the encoded buffer. + */ + public static short encodeToApduBuffer( + short object, byte[] apduBuf, short apduOff, short maxLen) { + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(maxLen); + short len = encoder.encode(object, repository.getHeap(), offset, prevReclaimIndex, maxLen); + Util.arrayCopyNonAtomic(repository.getHeap(), offset, apduBuf, apduOff, len); + // release memory + repository.reclaimMemory(maxLen); + return len; + } + + public static short validateCertChain( + boolean validateEekRoot, + byte expCertAlg, + byte expLeafCertAlg, + short certChainArr, + byte[] scratchPad, + Object[] authorizedEekRoots) { + short len = KMArray.cast(certChainArr).length(); + short coseHeadersExp = KMCoseHeaders.exp(); + // prepare exp for coseky + short coseKeyExp = KMCoseKey.exp(); + short ptr1; + short ptr2; + short signStructure; + short encodedLen; + short prevCoseKey = 0; + short keySize; + short alg = expCertAlg; + short index; + for (index = 0; index < len; index++) { + ptr1 = KMArray.cast(certChainArr).get(index); + + // validate protected Headers + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if (!KMCoseHeaders.cast(ptr2).isDataValid(rkp.rkpTmpVariables, alg, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // parse and get the public key from payload. + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if ((index == (short) (len - 1)) && len > 1) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2) + .isDataValid( + rkp.rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + alg, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 + == Util.arrayCompare( + scratchPad, + (short) 0, + (byte[]) authorizedEekRoots[i], + (short) 0, + (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short signatureLen = + rkp.encodeES256CoseSignSignature( + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getStartOff(), + KMByteBlob.length(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + scratchPad, + (short) (keySize + encodedLen)); + + if (!seProvider.ecVerify256( + scratchPad, + (short) 0, + keySize, + scratchPad, + keySize, + encodedLen, + scratchPad, + (short) (keySize + encodedLen), + signatureLen)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } + + public static short generateDiceCertChain(byte[] scratchPad) { + if (kmDataStore.isProvisionLocked()) { + KMException.throwIt(KMError.STATUS_FAILED); + } + KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(); + short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); + short coseKey = + KMCose.constructCoseKey( + rkp.rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + scratchPad, + (short) 0, + temp, + KMType.INVALID_VALUE, + false); + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct payload. + short payload = + KMCose.constructCoseCertPayload( + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.ISSUER), + KMTextString.instance( + KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)), + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.SUBJECT), + KMTextString.instance( + KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0), + KMByteBlob.instance(scratchPad, (short) 0, temp)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0), + KMByteBlob.instance( + KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length))); + // temp temporarily holds the length of encoded cert payload. + temp = + KMKeymasterApplet.encodeToApduBuffer( + payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + payload = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // protected header + short protectedHeader = + KMCose.constructHeaders( + rkp.rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // temp temporarily holds the length of encoded headers. + temp = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // unprotected headers. + short arr = KMArray.instance((short) 0); + short unprotectedHeader = KMCoseHeaders.instance(arr); + + // construct cose sign structure. + short coseSignStructure = + KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // temp temporarily holds the length of encoded sign structure. + // Encode cose Sign_Structure. + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // do sign + short len = + seProvider.signWithDeviceUniqueKey( + deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp); + coseSignStructure = KMByteBlob.instance(scratchPad, temp, len); + + // construct cose_sign1 + short coseSign1 = + KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure); + + // [Cose_Key, Cose_Sign1] + short dcc = KMArray.instance((short) 2); + KMArray.cast(dcc).add((short) 0, coseKey); + KMArray.cast(dcc).add((short) 1, coseSign1); + return dcc; + } + + protected void initHmacNonceAndSeed() { + short nonce = repository.alloc((short) 32); + seProvider.newRandomNumber( + repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + kmDataStore.initHmacNonce(repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + } + + private void releaseAllOperations() { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index].reset(); + index++; + } + } + + private KMOperationState reserveOperation(short algorithm, short opHandle) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].getAlgorithm() == KMType.INVALID_VALUE) { + opTable[index].reset(); + opTable[index].setAlgorithm(algorithm); + opTable[index].setHandle( + KMInteger.cast(opHandle).getBuffer(), + KMInteger.cast(opHandle).getStartOff(), + KMInteger.cast(opHandle).length()); + return opTable[index]; + } + index++; + } + return null; + } + + private KMOperationState findOperation(short handle) { + return findOperation( + KMInteger.cast(handle).getBuffer(), + KMInteger.cast(handle).getStartOff(), + KMInteger.cast(handle).length()); + } + + private KMOperationState findOperation(byte[] opHandle, short start, short len) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].compare(opHandle, start, len) == 0) { + if (opTable[index].getAlgorithm() != KMType.INVALID_VALUE) { + return opTable[index]; + } + } + index++; + } + return null; + } + + private void releaseOperation(KMOperationState op) { + op.reset(); + } + + /** + * Selects this applet. + * + * @return Returns true if the keymaster is in correct state + */ + @Override + public boolean select() { + repository.onSelect(); + return true; + } + + /** De-selects this applet. */ + @Override + public void deselect() { + repository.onDeselect(); + } + + /** Uninstalls the applet after cleaning the repository. */ + @Override + public void uninstall() { + repository.onUninstall(); + } + + protected short mapISOErrorToKMError(short reason) { + switch (reason) { + case ISO7816.SW_CLA_NOT_SUPPORTED: + return KMError.UNSUPPORTED_CLA; + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + return KMError.SW_CONDITIONS_NOT_SATISFIED; + case ISO7816.SW_COMMAND_NOT_ALLOWED: + return KMError.CMD_NOT_ALLOWED; + case ISO7816.SW_DATA_INVALID: + return KMError.INVALID_DATA; + case ISO7816.SW_INCORRECT_P1P2: + return KMError.INVALID_P1P2; + case ISO7816.SW_INS_NOT_SUPPORTED: + return KMError.UNSUPPORTED_INSTRUCTION; + case ISO7816.SW_WRONG_LENGTH: + return KMError.SW_WRONG_LENGTH; + case ISO7816.SW_UNKNOWN: + default: + return KMError.UNKNOWN_ERROR; + } + } + + protected short mapCryptoErrorToKMError(short reason) { + switch (reason) { + case CryptoException.ILLEGAL_USE: + return KMError.CRYPTO_ILLEGAL_USE; + case CryptoException.ILLEGAL_VALUE: + return KMError.CRYPTO_ILLEGAL_VALUE; + case CryptoException.INVALID_INIT: + return KMError.CRYPTO_INVALID_INIT; + case CryptoException.NO_SUCH_ALGORITHM: + return KMError.CRYPTO_NO_SUCH_ALGORITHM; + case CryptoException.UNINITIALIZED_KEY: + return KMError.CRYPTO_UNINITIALIZED_KEY; + default: + return KMError.UNKNOWN_ERROR; + } + } + + public void updateApduStatusFlags(short apduIns) { + switch (apduIns) { + case INS_EXPORT_KEY_CMD: + case INS_DELETE_ALL_KEYS_CMD: + case INS_DESTROY_ATT_IDS_CMD: + case INS_VERIFY_AUTHORIZATION_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_GET_HW_INFO_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_GET_ROT_DATA_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_UDS_CERTS_CMD: + case INS_GET_DICE_CERT_CHAIN_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + // By default the instruction is set to case 4 command instruction. + break; + } + } + + /** + * Processes an incoming APDU and handles it using command objects. + * + * @param apdu the incoming APDU + */ + @Override + public void process(APDU apdu) { + try { + resetTransientBuffers(); + repository.onProcess(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + byte[] apduBuffer = apdu.getBuffer(); + byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; + if (!isKeyMintReady(apduIns)) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + switch (apduIns) { + case INS_INIT_STRONGBOX_CMD: + processInitStrongBoxCmd(apdu); + sendResponse(apdu, KMError.OK); + return; + case INS_GENERATE_KEY_CMD: + processGenerateKey(apdu); + break; + case INS_IMPORT_KEY_CMD: + processImportKeyCmd(apdu); + break; + case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD: + processBeginImportWrappedKeyCmd(apdu); + break; + case INS_FINISH_IMPORT_WRAPPED_KEY_CMD: + processFinishImportWrappedKeyCmd(apdu); + break; + case INS_EXPORT_KEY_CMD: + processExportKeyCmd(apdu); + break; + case INS_UPGRADE_KEY_CMD: + processUpgradeKeyCmd(apdu); + break; + case INS_DELETE_KEY_CMD: + processDeleteKeyCmd(apdu); + break; + case INS_DELETE_ALL_KEYS_CMD: + processDeleteAllKeysCmd(apdu); + break; + case INS_ADD_RNG_ENTROPY_CMD: + processAddRngEntropyCmd(apdu); + break; + case INS_COMPUTE_SHARED_HMAC_CMD: + processComputeSharedHmacCmd(apdu); + break; + case INS_DESTROY_ATT_IDS_CMD: + processDestroyAttIdsCmd(apdu); + break; + case INS_VERIFY_AUTHORIZATION_CMD: + processVerifyAuthorizationCmd(apdu); + break; + case INS_GET_HMAC_SHARING_PARAM_CMD: + processGetHmacSharingParamCmd(apdu); + break; + case INS_GET_KEY_CHARACTERISTICS_CMD: + processGetKeyCharacteristicsCmd(apdu); + break; + case INS_GET_HW_INFO_CMD: + processGetHwInfoCmd(apdu); + break; + case INS_BEGIN_OPERATION_CMD: + processBeginOperationCmd(apdu); + break; + case INS_UPDATE_OPERATION_CMD: + processUpdateOperationCmd(apdu); + break; + case INS_FINISH_OPERATION_CMD: + processFinishOperationCmd(apdu); + break; + case INS_ABORT_OPERATION_CMD: + processAbortOperationCmd(apdu); + break; + case INS_DEVICE_LOCKED_CMD: + processDeviceLockedCmd(apdu); + break; + case INS_EARLY_BOOT_ENDED_CMD: + processEarlyBootEndedCmd(apdu); + break; + case INS_UPDATE_AAD_OPERATION_CMD: + processUpdateAadOperationCmd(apdu); + break; + case INS_GENERATE_RKP_KEY_CMD: + case INS_BEGIN_SEND_DATA_CMD: + case INS_UPDATE_KEY_CMD: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_GET_UDS_CERTS_CMD: + case INS_GET_DICE_CERT_CHAIN_CMD: + rkp.process(apduIns, apdu); + break; + // KeyMint 2.0 + case INS_GET_ROT_CHALLENGE_CMD: + processGetRootOfTrustChallenge(apdu); + break; + case INS_GET_ROT_DATA_CMD: + sendResponse(apdu, KMError.UNIMPLEMENTED); + break; + case INS_SEND_ROT_DATA_CMD: + processSendRootOfTrust(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (KMException exception) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private void processGetRootOfTrustChallenge(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + // Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of + // trust data. + seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16); + kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16); + short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16); + short arr = KMArray.instance((short) 2); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, challenge); + sendOutgoing(apdu, arr); + } + + private short sendRootOfTrustCmd(APDU apdu) { + short arrInst = KMArray.instance((short) 4); + short headers = KMCoseHeaders.exp(); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short semanticTag = KMSemanticTag.exp(arrInst); + short arr = KMArray.exp(semanticTag); + return receiveIncoming(apdu, arr); + } + + private void processSendRootOfTrust(APDU apdu) { + byte[] scratchPad = apdu.getBuffer(); + short cmd = KMType.INVALID_VALUE; + // As per VTS if the input data is empty or not well-formed + // CoseMac return VERIFICATION_FAILED error. + try { + cmd = sendRootOfTrustCmd(apdu); + } catch (Exception e) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + short semanticTag = KMArray.cast(cmd).get((short) 0); + short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr(); + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + // Validate the Mac + short len = kmDataStore.getChallenge(scratchPad, (short) 0); + short extAad = KMByteBlob.instance(scratchPad, (short) 0, len); + // Compute CoseMac Structure and compare the macs. + short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + extAad, + rotPayload); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + if (!seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + // Store the data only once after reboot. + // Allow set boot params only when the host device reboots and the applet is in + // active state. If host does not support boot signal event, then allow this + // instruction any time. + kmDataStore.getDeviceBootStatus(scratchPad, (short) 0); + if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) { + // store the data. + storeRootOfTrust(rotPayload, scratchPad); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS); + } + // Invalidate the challenge + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16); + sendResponse(apdu, KMError.OK); + } + + private void storeRootOfTrust(short rotPayload, byte[] scratchPad) { + short byteBlobExp = KMByteBlob.exp(); + short intExp = KMInteger.exp(); + short boolExp = KMSimpleValue.exp(); + short arr = KMArray.instance((short) 5); + KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key. + KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked. + KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State. + KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash. + KMArray.cast(arr).add((short) 4, intExp); // Boot patch level + short semanticExp = KMSemanticTag.exp(arr); + + short semanticPtr = + decoder.decode( + semanticExp, + KMByteBlob.cast(rotPayload).getBuffer(), + KMByteBlob.cast(rotPayload).getStartOff(), + KMByteBlob.cast(rotPayload).length()); + short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr(); + // Store verified boot key + short ptr = KMArray.cast(rotArr).get((short) 0); + kmDataStore.setBootKey( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store Boot device locked. + ptr = KMArray.cast(rotArr).get((short) 1); + kmDataStore.setDeviceLocked( + (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false); + // Store verified boot state + ptr = KMArray.cast(rotArr).get((short) 2); + kmDataStore.setBootState(KMInteger.cast(ptr).getShort()); + // Store Verified boot hash + ptr = KMArray.cast(rotArr).get((short) 3); + kmDataStore.setVerifiedBootHash( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store boot patch level + ptr = KMArray.cast(rotArr).get((short) 4); + kmDataStore.setBootPatchLevel( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + } + + // After every device boot, the Keymaster becomes ready to execute all the commands only after + // 1. boot parameters are set, + // 2. system properties are set and + // 3. computed the shared secret successfully. + private boolean isKeyMintReady(byte apduIns) { + if (kmDataStore.isDeviceReady()) { + return true; + } + // Below commands are allowed even if the Keymaster is not ready. + switch (apduIns) { + case INS_GET_HW_INFO_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_ADD_RNG_ENTROPY_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_COMPUTE_SHARED_HMAC_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_INIT_STRONGBOX_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_SEND_ROT_DATA_CMD: + return true; + default: + break; + } + return false; + } + + private void generateUniqueOperationHandle(byte[] buf, short offset, short len) { + do { + seProvider.newRandomNumber(buf, offset, len); + } while (null != findOperation(buf, offset, len)); + } + + private void freeOperations() { + if (data[OP_HANDLE] != KMType.INVALID_VALUE) { + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op != null) { + releaseOperation(op); + } + } + } + + private void processEarlyBootEndedCmd(APDU apdu) { + kmDataStore.setEarlyBootEndedStatus(true); + sendResponse(apdu, KMError.OK); + } + + private short deviceLockedCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short ptr = KMVerificationToken.exp(); + // passwordOnly + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + // verification token + KMArray.cast(cmd).add((short) 1, ptr); + return receiveIncoming(apdu, cmd); + } + + private void processDeviceLockedCmd(APDU apdu) { + short cmd = deviceLockedCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short passwordOnly = KMArray.cast(cmd).get((short) 0); + short verToken = KMArray.cast(cmd).get((short) 1); + passwordOnly = KMInteger.cast(passwordOnly).getByte(); + validateVerificationToken(verToken, scratchPad); + short verTime = KMVerificationToken.cast(verToken).getTimestamp(); + short lastDeviceLockedTime; + try { + lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp(); + } catch (KMException e) { + lastDeviceLockedTime = KMInteger.uint_8((byte) 0); + } + if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0); + KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64); + kmDataStore.setDeviceLock(true); + kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01); + kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64); + } + sendResponse(apdu, KMError.OK); + } + + private void resetWrappingKey() { + if (!isValidWrappingKey()) { + return; + } + Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0); + wrappingKey[0] = -1; + } + + private boolean isValidWrappingKey() { + return wrappingKey[0] != -1; + } + + private short getWrappingKey() { + return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE); + } + + private void setWrappingKey(short key) { + if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + wrappingKey[0] = 0; + Util.arrayCopyNonAtomic( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + wrappingKey, + (short) 1, + WRAPPING_KEY_SIZE); + } + + protected void resetTransientBuffers() { + short index = 0; + while (index < data.length) { + data[index] = KMType.INVALID_VALUE; + index++; + } + index = 0; + while (index < tmpVariables.length) { + tmpVariables[index] = KMType.INVALID_VALUE; + index++; + } + } + + public void sendOutgoing( + APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) { + // This is the special case where the output is encoded manually without using + // the encoder algorithm. Encoder creates a duplicate copy for each KMType Object. + // The output of the generateKey, importKey and importWrappedKey commands are huge so + // by manually encoding we can avoid duplicate copies. + // The output data is directly written to the end of heap in the below order + // output = [ + // errorCode : uint // ErrorCode + // keyBlob : bstr // KeyBlob. + // keyChars + // certifcate + // ] + // certificate = [ + // x509_cert : bstr // X509 certificate + // ] + // keyChars = { // Map + // } + byte[] buffer = repository.getHeap(); + + if (cert == null) { + // This happens for Symmetric keys. + short bufferStart = repository.allocReclaimableMemory((short) 1); + buffer[bufferStart] = (byte) 0x80; // Array of 0 length. + } else { + // Encode the certificate into cbor data at the end of the heap + // certData = [ + // x509_cert : bstr // X509 certificate + // ] + short bufferStart = + encoder.encodeCert( + repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength()); + // reclaim the unused memory in the certificate. + repository.reclaimMemory((short) (bufferStart - certStart)); + } + + // Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE] + encodeKeyCharacteristics(keyChars); + // and encode it to the end of the buffer before KEY_CHARACTERISTICS + encodeKeyBlob(keyblob); + // Write Array header and ErrorCode before data[KEY_BLOB] + short bufferStartOffset = repository.allocReclaimableMemory((short) 2); + Util.setShort(buffer, bufferStartOffset, (short) 0x8400); + + short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset); + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + private void processGetHwInfoCmd(APDU apdu) { + // No arguments expected + final byte version = 3; + // Make the response + short respPtr = KMArray.instance((short) 6); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(KMError.OK)); + resp.add((short) 1, KMInteger.uint_8(version)); + resp.add((short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + resp.add( + (short) 3, + KMByteBlob.instance( + JavacardKeymintDevice, (short) 0, (short) JavacardKeymintDevice.length)); + resp.add((short) 4, KMByteBlob.instance(Google, (short) 0, (short) Google.length)); + resp.add((short) 5, KMInteger.uint_8((byte) 1)); + // send buffer to host + sendOutgoing(apdu, respPtr); + } + + private short addRngEntropyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + // Rng entropy + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAddRngEntropyCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = addRngEntropyCmd(apdu); + // Process + KMByteBlob blob = KMByteBlob.cast(KMArray.cast(cmd).get((short) 0)); + // Maximum 2KiB of seed is allowed. + if (blob.length() > MAX_SEED_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + seProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length()); + sendResponse(apdu, KMError.OK); + } + + private short getKeyCharacteristicsCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processGetKeyCharacteristicsCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = getKeyCharacteristicsCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0); + data[APP_ID] = KMArray.cast(cmd).get((short) 1); + data[APP_DATA] = KMArray.cast(cmd).get((short) 2); + if (KMByteBlob.cast(data[APP_ID]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE + || KMByteBlob.cast(data[APP_DATA]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!KMByteBlob.cast(data[APP_ID]).isValid()) { + data[APP_ID] = KMType.INVALID_VALUE; + } + if (!KMByteBlob.cast(data[APP_DATA]).isValid()) { + data[APP_DATA] = KMType.INVALID_VALUE; + } + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // make response. + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_CHARACTERISTICS]); + sendOutgoing(apdu, resp); + } + + private void processGetHmacSharingParamCmd(APDU apdu) { + // No Arguments + // Create HMAC Sharing Parameters + short params = KMHmacSharingParameters.instance(); + short nonce = kmDataStore.getHmacNonce(); + short seed = KMByteBlob.instance((short) 0); + KMHmacSharingParameters.cast(params).setNonce(nonce); + KMHmacSharingParameters.cast(params).setSeed(seed); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + sendOutgoing(apdu, resp); + } + + private void processDeleteAllKeysCmd(APDU apdu) { + // No arguments + // This function is triggered when a factory reset event occurs. + // Regenerate the master key to render all keys unusable. + kmDataStore.regenerateMasterKey(); + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short createKeyBlobExp(short version) { + short keyBlob = KMType.INVALID_VALUE; + short byteBlobExp = KMByteBlob.exp(); + short keyChar = KMKeyCharacteristics.exp(); + short keyParam = KMKeyParameters.exp(); + switch (version) { + case (short) 0: + // Old KeyBlob has a maximum of 5 elements. + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V0); + KMArray.cast(keyBlob).add((short) 0, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 3, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 4, byteBlobExp); // PubKey + break; + case (short) 1: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V1); + KMArray.cast(keyBlob).add((short) 0, KMInteger.exp()); // Version + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 3, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 4, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 5, byteBlobExp); // PubKey + break; + case (short) 2: + case (short) 3: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_VERSION_OFFSET, KMInteger.exp()); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_SECRET, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_NONCE, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PARAMS, keyChar); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_CUSTOM_TAGS, keyParam); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, byteBlobExp); + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return keyBlob; + } + + private void processDeleteKeyCmd(APDU apdu) { + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short computeSharedHmacCmd(APDU apdu) { + short params = KMHmacSharingParameters.exp(); + short paramsVec = KMArray.exp(params); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, paramsVec); + return receiveIncoming(apdu, cmd); + } + + private void processComputeSharedHmacCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = computeSharedHmacCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[HMAC_SHARING_PARAMS] = KMArray.cast(cmd).get((short) 0); + // Concatenate HMAC Params + // tmpVariables[0] + short paramsLen = KMArray.cast(data[HMAC_SHARING_PARAMS]).length(); // total number of params + // tmpVariables[1] + short concateBuffer = repository.alloc((short) (paramsLen * HMAC_SHARED_PARAM_MAX_SIZE)); + // tmpVariables[2] + short paramIndex = 0; // index for params + // tmpVariables[3] + short bufferIndex = 0; // index for concatenation buffer + // To check if nonce created by Strongbox is found. This value becomes 1 if both + // seed and nonce created here are found in hmac sharing parameters received. + // tmpVariables[7] = 0; + short found = 0; + // tmpVariables[9] + short nonce = kmDataStore.getHmacNonce(); + + while (paramIndex < paramsLen) { + // read HmacSharingParam + // tmpVariables[4] + short param = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(paramIndex); + // get seed - 32 bytes max + // tmpVariables[5] + short seed = KMHmacSharingParameters.cast(param).getSeed(); + // tmpVariables[6] + short seedLength = KMByteBlob.cast(seed).length(); + // if seed is present + if (seedLength != 0) { + // then copy that to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(seed).getBuffer(), + KMByteBlob.cast(seed).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // concat index + seedLength); + bufferIndex += seedLength; // increment the concat index + } else if (found == 0) { + found = 1; // Applet does not have any seed. Potentially + } + // if nonce is present get nonce - 32 bytes + // tmpVariables[5] + short paramNonce = KMHmacSharingParameters.cast(param).getNonce(); + short nonceLen = KMByteBlob.cast(paramNonce).length(); + // if nonce is less then 32 - it is an error + if (nonceLen < 32) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // copy nonce to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(paramNonce).getBuffer(), + KMByteBlob.cast(paramNonce).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // index + nonceLen); + + // Check if the nonce generated here is present in the hmacSharingParameters array. + // Otherwise throw INVALID_ARGUMENT error. + if (found == 1) { + if (0 + == Util.arrayCompare( + repository.getHeap(), + (short) (concateBuffer + bufferIndex), + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + nonceLen)) { + found = 2; // hmac nonce for this keymaster found. + } else { + found = 0; + } + } + bufferIndex += nonceLen; // increment by nonce length + paramIndex++; // go to next hmac param in the vector + } + if (found != 2) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // generate the key and store it in scratch pad - 32 bytes + // tmpVariables[6] + short keyLen = + seProvider.cmacKDF( + kmDataStore.getPresharedKey(), + ckdfLabel, + (short) 0, + (short) ckdfLabel.length, + repository.getHeap(), + concateBuffer, + bufferIndex, + scratchPad, + (short) 0); + + // persist the computed hmac key. + kmDataStore.createComputedHmacKey(scratchPad, (short) 0, keyLen); + // Generate sharingKey verification signature and store that in scratch pad. + // tmpVariables[5] + short signLen = + seProvider.hmacSign( + scratchPad, + (short) 0, + keyLen, + sharingCheck, + (short) 0, + (short) sharingCheck.length, + scratchPad, + keyLen); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.NEGOTIATED_SHARED_SECRET_SUCCESS); + // verification signature blob - 32 bytes + // tmpVariables[1] + short signature = KMByteBlob.instance(scratchPad, keyLen, signLen); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, signature); + sendOutgoing(apdu, resp); + } + + private short upgradeKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short keyParams = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Key Blob + KMArray.cast(cmd).add((short) 1, keyParams); // Key Params + return receiveIncoming(apdu, cmd); + } + + private boolean isKeyUpgradeRequired( + short keyBlob, short appId, short appData, byte[] scratchPad) { + // Check if the KeyBlob is compatible. If there is any change in the KeyBlob, the version + // Parameter in the KeyBlob should be updated to the next version. + short version = readKeyBlobVersion(keyBlob); + parseEncryptedKeyBlob(keyBlob, appId, appData, scratchPad, version); + if (version < KEYBLOB_CURRENT_VERSION) { + return true; + } + short bootPatchLevel = kmDataStore.getBootPatchLevel(); + // Fill the key-value properties in the scratchpad + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + Util.setShort(scratchPad, (short) 0, KMType.OS_VERSION); + Util.setShort(scratchPad, (short) 2, kmDataStore.getOsVersion()); + Util.setShort(scratchPad, (short) 4, KMType.OS_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 6, kmDataStore.getOsPatch()); + Util.setShort(scratchPad, (short) 8, KMType.VENDOR_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 10, kmDataStore.getVendorPatchLevel()); + Util.setShort(scratchPad, (short) 12, KMType.BOOT_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 14, bootPatchLevel); + short index = 0; + short tag; + short systemParam; + boolean isKeyUpgradeRequired = false; + while (index < 16) { + tag = Util.getShort(scratchPad, index); + systemParam = Util.getShort(scratchPad, (short) (index + 2)); + // validate the tag and check if key needs upgrade. + short tagValue = KMKeyParameters.findTag(KMType.UINT_TAG, tag, data[HW_PARAMETERS]); + tagValue = KMIntegerTag.cast(tagValue).getValue(); + short zero = KMInteger.uint_8((byte) 0); + if (tagValue != KMType.INVALID_VALUE) { + // OS version in key characteristics must be less the OS version stored in Javacard or the + // stored version must be zero. Then only upgrade is allowed else it is invalid argument. + if ((tag == KMType.OS_VERSION + && KMInteger.compare(tagValue, systemParam) == 1 + && KMInteger.compare(systemParam, zero) == 0)) { + // Key needs upgrade. + isKeyUpgradeRequired = true; + } else if ((KMInteger.compare(tagValue, systemParam) == -1)) { + // Each os version or patch level associated with the key must be less than it's + // corresponding value stored in Javacard, then only upgrade is allowed otherwise it + // is invalid argument. + isKeyUpgradeRequired = true; + } else if (KMInteger.compare(tagValue, systemParam) == 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index += 4; + } + return isKeyUpgradeRequired; + } + + private void processUpgradeKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = upgradeKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + + short keyBlob = KMArray.cast(cmd).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + short appId = getApplicationId(data[KEY_PARAMETERS]); + short appData = getApplicationData(data[KEY_PARAMETERS]); + + data[KEY_BLOB] = KMType.INVALID_VALUE; + // Check if the KeyBlob requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself, but if there is a difference in the KeyBlob version isKeyUpgradeRequired() + // does not parse the KeyBlob. + boolean isKeyUpgradeRequired = isKeyUpgradeRequired(keyBlob, appId, appData, scratchPad); + if (isKeyUpgradeRequired) { + // copy origin + data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]); + byte keyType = getKeyType(data[HW_PARAMETERS]); + switch (keyType) { + case ASYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + break; + case SYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(SYM_KEY_BLOB_SIZE_V2_V3); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + // Update the system properties to the latest values and also re-create the KeyBlob's + // KeyCharacteristics to make sure all the values are up-to-date with the latest applet + // changes. + upgradeKeyBlobKeyCharacteristics(data[HW_PARAMETERS], scratchPad); + // create new key blob with current os version etc. + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } else { + data[KEY_BLOB] = KMByteBlob.instance((short) 0); + } + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); + sendOutgoing(apdu, resp); + } + + private void processExportKeyCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) { + // Read App Id and App Data if any from un wrapping key params + data[APP_ID] = getApplicationId(wrapParams); + data[APP_DATA] = getApplicationData(wrapParams); + data[KEY_PARAMETERS] = wrapParams; + data[KEY_BLOB] = keyBlob; + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + validateWrappingKeyBlob(); + } + + private void validateWrappingKeyBlob() { + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest + // SHA2_256. + KMTag.assertPresence( + data[SB_PARAMETERS], + KMType.ENUM_TAG, + KMType.ALGORITHM, + KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + } + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, data[HW_PARAMETERS])) { + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + + // Check that the digest and padding mode specified in unwrapping parameters are SHA2_256 + // and RSA_OAEP respectively. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + + private short decryptTransportKey( + short privExp, short modulus, short transportKey, byte[] scratchPad) { + short length = + seProvider.rsaDecipherOAEP256( + KMByteBlob.cast(privExp).getBuffer(), + KMByteBlob.cast(privExp).getStartOff(), + KMByteBlob.cast(privExp).length(), + KMByteBlob.cast(modulus).getBuffer(), + KMByteBlob.cast(modulus).getStartOff(), + KMByteBlob.cast(modulus).length(), + KMByteBlob.cast(transportKey).getBuffer(), + KMByteBlob.cast(transportKey).getStartOff(), + KMByteBlob.cast(transportKey).length(), + scratchPad, + (short) 0); + return KMByteBlob.instance(scratchPad, (short) 0, length); + } + + private void unmask(short data, short maskingKey) { + short dataLength = KMByteBlob.cast(data).length(); + short maskLength = KMByteBlob.cast(maskingKey).length(); + // Length of masking key and transport key must be same. + if (maskLength != dataLength) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (index < maskLength) { + short var1 = (short) (((short) KMByteBlob.cast(maskingKey).get(index)) & 0x00FF); + short var2 = (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF); + KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2)); + index++; + } + } + + private short beginImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Masking Key + params = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params + return receiveIncoming(apdu, cmd); + } + + private void processBeginImportWrappedKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginImportWrappedKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + // Step -1 parse the wrapping key blob + // read wrapping key blob + short keyBlob = KMArray.cast(cmd).get((short) 1); + // read un wrapping key params + short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3); + processWrappingKeyBlob(keyBlob, wrappingKeyParameters, scratchPad); + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + short transportKey = + decryptTransportKey( + data[SECRET], data[PUB_KEY], KMArray.cast(cmd).get((short) 0), scratchPad); + // Step 3 - XOR the decrypted AES-GCM key with with masking key + unmask(transportKey, KMArray.cast(cmd).get((short) 2)); + if (isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + setWrappingKey(transportKey); + sendResponse(apdu, KMError.OK); + } + + private short aesGCMEncrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + short len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length()); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short aesGCMDecrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + } + + private short finishImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 8); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid + KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid + return receiveIncoming(apdu, cmd); + } + + // TODO remove cmd later on + private void processFinishImportWrappedKeyCmd(APDU apdu) { + short cmd = finishImportWrappedKeyCmd(apdu); + short keyParameters = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + keyFmt = KMEnum.cast(keyFmt).getVal(); + validateImportKey(keyParameters, keyFmt); + byte[] scratchPad = apdu.getBuffer(); + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); + data[NONCE] = KMArray.cast(cmd).get((short) 4); + data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5); + + if (!isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + data[IMPORTED_KEY_BLOB] = + aesGCMDecrypt( + getWrappingKey(), + data[INPUT_DATA], + data[NONCE], + data[AUTH_DATA], + data[AUTH_TAG], + scratchPad); + resetWrappingKey(); + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = keyParameters; + // create key blob array + importKey(apdu, keyFmt, scratchPad); + } + + private KMAttestationCert makeCommonCert(byte[] scratchPad) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + cert.subjectName(subject); + // Validity period must be specified + short notBefore = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.cast(notBefore).getValue(); + short notAfter = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.cast(notAfter).getValue(); + // VTS sends notBefore == Epoch. + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); + short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8); + short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length); + if (KMInteger.compare(notBefore, epoch) == 0) { + cert.notBefore( + KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad); + } else { + cert.notBefore(notBefore, false, scratchPad); + } + // VTS sends notAfter == Dec 31st 9999 + if (KMInteger.compare(notAfter, end) == 0) { + cert.notAfter( + KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad); + } else { + cert.notAfter(notAfter, false, scratchPad); + } + // Serial number + short serialNum = + KMKeyParameters.findTag( + KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]); + if (serialNum != KMType.INVALID_VALUE) { + serialNum = KMBignumTag.cast(serialNum).getValue(); + } else { + serialNum = KMByteBlob.instance((short) 1); + KMByteBlob.cast(serialNum).add((short) 0, (byte) 1); + } + cert.serialNumber(serialNum); + return cert; + } + + private KMAttestationCert makeAttestationCert( + short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + + // Read App Id and App Data. + short appId = getApplicationId(attKeyParam); + short appData = getApplicationData(attKeyParam); + // Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR + // and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function. + short origBlob = data[KEY_BLOB]; + short pubKey = data[PUB_KEY]; + short privKey = data[SECRET]; + short hwParams = data[HW_PARAMETERS]; + short keyChars = data[KEY_CHARACTERISTICS]; + short customTags = data[CUSTOM_TAGS]; + // Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside + // isKeyUpgradeRequired function itself. + if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // Get the private key of the attest key. + short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET); + // Get the KeyCharacteristics and SB param of the attest key + short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS); + short attestKeySbParams = + KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced(); + // If the attest key's purpose is not "attest key" then error. + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams); + if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + try { + asn1Decoder.validateDerSubject(issuer); + } catch (KMException e) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + if (KMByteBlob.cast(issuer).length() > KMConfigurations.MAX_SUBJECT_DER_LEN) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + // If issuer is not present then it is an error + if (KMByteBlob.cast(issuer).length() <= 0) { + KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, attestKeySbParams); + if (alg == KMType.RSA) { + short attestationKeyPublic = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY); + cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT); + } else if (alg == KMType.EC) { + cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT); + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + cert.attestationChallenge(attChallenge); + cert.issuer(issuer); + + // Restore back the global variables. + data[PUB_KEY] = pubKey; + data[SECRET] = privKey; + data[KEY_BLOB] = origBlob; + data[HW_PARAMETERS] = hwParams; + data[KEY_CHARACTERISTICS] = keyChars; + data[CUSTOM_TAGS] = customTags; + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + cert.publicKey(data[PUB_KEY]); + + // Save attestation application id - must be present. + short attAppId = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + if (attAppId == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + } + cert.extensionTag(attAppId, false); + // unique id byte blob - uses application id and temporal month count of + // creation time. + attAppId = KMByteTag.cast(attAppId).getValue(); + setUniqueId(cert, attAppId, scratchPad); + // Add Attestation Ids if present + addAttestationIds(cert, scratchPad); + + // Add Tags + addTags(data[HW_PARAMETERS], true, cert); + addTags(data[SW_PARAMETERS], false, cert); + // Add Device Boot locked status + cert.deviceLocked(kmDataStore.isDeviceBootLocked()); + // VB data + cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); + cert.verifiedBootKey(getBootKey(scratchPad)); + cert.verifiedBootState((byte) kmDataStore.getBootState()); + return cert; + } + + private KMAttestationCert makeSelfSignedCert( + short attPrivKey, short attPubKey, short mode, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + + if (alg == KMType.RSA) { + cert.rsaAttestKey(attPrivKey, attPubKey, (byte) mode); + } else { + cert.ecAttestKey(attPrivKey, (byte) mode); + } + cert.issuer(subject); + cert.subjectName(subject); + cert.publicKey(attPubKey); + return cert; + } + + protected short getBootKey(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE); + } + + protected short getVerifiedBootHash(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE, (byte) 0); + short len = kmDataStore.getVerifiedBootHash(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_HASH_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE); + } + + // -------------------------------- + // Only add the Attestation ids which are requested in the attestation parameters. + // If the requested attestation ids are not provisioned or deleted then + // throw CANNOT_ATTEST_IDS error. If there is mismatch in the attestation + // id values of both the requested parameters and the provisioned parameters + // then throw INVALID_TAG error. + private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) { + byte index = 0; + short attIdTag; + short attIdTagValue; + short storedAttIdLen; + while (index < (short) attTags.length) { + attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + attIdTagValue = KMByteTag.cast(attIdTag).getValue(); + storedAttIdLen = kmDataStore.getAttestationId(attTags[index], scratchPad, (short) 0); + // Return CANNOT_ATTEST_IDS if Attestation IDs are not provisioned or + // Attestation IDs are deleted. + if (storedAttIdLen == 0) { + // Ignore the SECOND_IMEI tag if the previous Applet's KeyMint version is less than + // 3.0 and no SECOND_IMEI is provisioned. + if (!(kmDataStore.ignoreSecondImei + && attTags[index] == KMType.ATTESTATION_ID_SECOND_IMEI)) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + } else { + // Return INVALID_TAG if Attestation IDs does not match. + if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length()) + || (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(attIdTagValue).getBuffer(), + KMByteBlob.cast(attIdTagValue).getStartOff(), + storedAttIdLen))) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen); + cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); + } + } + index++; + } + } + + private void processDestroyAttIdsCmd(APDU apdu) { + kmDataStore.deleteAttestationIds(); + sendResponse(apdu, KMError.OK); + } + + private void processVerifyAuthorizationCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private short abortOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAbortOperationCmd(APDU apdu) { + short cmd = abortOperationCmd(apdu); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE); + } else { + releaseOperation(op); + sendResponse(apdu, KMError.OK); + } + } + + private short finishOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); // auth token + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 4, verToken); // time stamp token + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token + return receiveIncoming(apdu, cmd); + } + + private void processFinishOperationCmd(APDU apdu) { + short cmd = finishOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[SIGNATURE] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); + data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // Authorize the finish operation + authorizeUpdateFinishOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + finishTrustedConfirmationOperation(op); + case KMType.VERIFY: + finishSigningVerifyingOperation(op, scratchPad); + break; + case KMType.ENCRYPT: + finishEncryptOperation(op, scratchPad); + break; + case KMType.DECRYPT: + finishDecryptOperation(op, scratchPad); + break; + case KMType.AGREE_KEY: + finishKeyAgreementOperation(op, scratchPad); + break; + } + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Remove the operation handle + releaseOperation(op); + + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { + if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + finishAesDesOperation(op); + } + + private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch (op.getAlgorithm()) { + case KMType.RSA: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + if (op.getPadding() == KMType.PADDING_NONE && len != 256) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + break; + case KMType.AES: + case KMType.DES: + finishAesDesOperation(op); + break; + } + } + + private void finishAesDesOperation(KMOperationState op) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = AES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.DES) { + blockSize = DES_BLOCK_SIZE; + } + + if (op.getPurpose() == KMType.DECRYPT + && len > 0 + && (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC) + && ((short) (len % blockSize) != 0)) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + if (op.getBlockMode() == KMType.GCM) { + if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + // Get the output size + len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8)); + } + // If padding i.e. pkcs7 then add padding to right + // Output data can at most one block size more the input data in case of pkcs7 encryption + // In case of gcm we will allocate extra memory of the size equal to blocksize. + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + try { + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + if (e.getReason() == CryptoException.ILLEGAL_USE) { + // As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length + // output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is + // provided via update() method. So ignore this exception in case if all below conditions + // are satisfied and simply return empty output. + // 1. padding mode is PADDING_NONE. + // 2. No input message is processed in update(). + // 3. Zero length input data is passed in finish operation. + if ((op.getPadding() == KMType.PADDING_NONE) + && !op.isInputMsgProcessed() + && (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) { + len = 0; + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + } + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = + op.getOperation() + .finish( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.cast(blob).length(), + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + switch (op.getAlgorithm()) { + case KMType.RSA: + // If there is no padding we can treat signing as a RSA decryption operation. + try { + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 256 bytes - but it can be less then 256 bytes + short len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + // Maximum output size of signature is 256 bytes. - the signature will always be + // positive + data[OUTPUT_DATA] = KMByteBlob.instance((short) 256); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + (short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len), + len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.EC: + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + // If DIGEST NONE then truncate the input data to 32 bytes. + if (op.getDigest() == KMType.DIGEST_NONE && len > 32) { + len = 32; + } + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 512 bits i.e. 64 bytes + len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + if (op.getPurpose() == KMType.SIGN) { + // Copy only signature of mac length size. + data[OUTPUT_DATA] = + KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8)); + } else if (op.getPurpose() == KMType.VERIFY) { + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8)) + || KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + + if (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + break; + default: // This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } + } + + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // If one time user Authentication is required + if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { + // Validate Verification Token. + validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); + // validate operation handle. + short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + if (KMInteger.compare(ptr, op.getHandle()) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + tmpVariables[0] = op.getAuthTime(); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + if (!authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + } + + private void authorizeKeyUsageForCount(byte[] scratchPad) { + short scratchPadOff = 0; + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0); + + short usageLimitBufLen = + KMIntegerTag.getValue( + scratchPad, + scratchPadOff, + KMType.UINT_TAG, + KMType.MAX_USES_PER_BOOT, + data[HW_PARAMETERS]); + + if (usageLimitBufLen == KMType.INVALID_VALUE) { + return; + } + + if (usageLimitBufLen > 4) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) { + // Get current counter, update and increment it. + short len = + kmDataStore.getRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4)); + if (len != 4) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (0 + >= KMInteger.unsignedByteArrayCompare( + scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) { + KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED); + } + // Increment the counter. + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0); + Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1); + KMUtils.add( + scratchPad, + scratchPadOff, + (short) (scratchPadOff + len), + (short) (scratchPadOff + len * 2)); + + kmDataStore.setRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len); + } else { + // Persist auth tag. + if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + } + } + + private void authorizeDeviceUnlock(byte[] scratchPad) { + // If device is locked and key characteristics requires unlocked device then check whether + // HW auth token has correct timestamp. + short ptr = + KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]); + + if (ptr != KMType.INVALID_VALUE && kmDataStore.getDeviceLock()) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(); + // Check if the current auth time stamp is greater than device locked time stamp + short ts = kmDataStore.getDeviceTimeStamp(); + if (KMInteger.compare(ptr, ts) <= 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + // Now check if the device unlock requires password only authentication and whether + // auth token is generated through password authentication or not. + if (kmDataStore.getDeviceLockPasswordOnly()) { + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + } + // Unlock the device + // repository.deviceLockedFlag = false; + kmDataStore.setDeviceLock(false); + kmDataStore.clearDeviceLockTimeStamp(); + } + } + + private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopyNonAtomic( + authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; + // concatenate challenge - 8 bytes + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate security level - 4 bytes + scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; + len += KMInteger.UINT_32; + // hmac the data + ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private void validateVerificationToken(short verToken, byte[] scratchPad) { + short ptr = KMVerificationToken.cast(verToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private short updateOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateOperationCmd(APDU apdu) { + short cmd = updateOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { + // update the data. + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + // update trusted confirmation operation + updateTrustedConfirmationOperation(op); + + data[OUTPUT_DATA] = KMType.INVALID_VALUE; + } else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + // Update for encrypt/decrypt using RSA will not be supported because to do this op state + // will have to buffer the data - so reject the update if it is rsa algorithm. + if (op.getAlgorithm() == KMType.RSA) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = DES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.AES) { + blockSize = AES_BLOCK_SIZE; + if (op.getBlockMode() == KMType.GCM) { + // if input data present + if (len > 0) { + // no more future updateAAD allowed if input data present. + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + } + } + } + // Allocate output buffer as input data is already block aligned + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + // Otherwise just update the data. + // HAL consumes all the input and maintains a buffered data inside it. So the + // applet sends the inputConsumed length as same as the input length. + try { + len = + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) { + // This flag is used to denote that an input data of length > 0 is received and processed + // successfully in update command. This flag is later used in the finish operation + // to handle a particular use case, where a zero length input data on AES/DES algorithm + // with PADDING_NONE should return a zero length output with OK response. + op.setProcessedInputMsg(true); + } + // Adjust the Output data if it is not equal to input data. + // This happens in case of JCardSim provider. + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Persist if there are any updates. + // op.persist(); + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private short updateAadOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateAadOperationCmd(APDU apdu) { + short cmd = updateAadOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + if (op.getAlgorithm() != KMType.AES) { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + if (op.getBlockMode() != KMType.GCM) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + try { + op.getOperation() + .updateAAD( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make response + short resp = KMArray.instance((short) 1); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + sendOutgoing(apdu, resp); + } + + private short beginOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, params); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); + return receiveIncoming(apdu, cmd); + } + + private void processBeginOperationCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short purpose = KMArray.cast(cmd).get((short) 0); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + purpose = KMEnum.cast(purpose).getVal(); + // Check for app id and app data. + data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]); + data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]); + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + KMTag.assertPresence( + data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM); + short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]); + // If Blob usage tag is present in key characteristics then it should be standalone. + if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) { + if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + } + + // Generate a random number for operation handle + short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE); + generateUniqueOperationHandle( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + /* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */ + short opHandle = + KMInteger.instance( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + KMOperationState op = reserveOperation(algorithm, opHandle); + if (op == null) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + data[OP_HANDLE] = op.getHandle(); + op.setPurpose((byte) purpose); + op.setKeySize(KMByteBlob.cast(data[SECRET]).length()); + authorizeAndBeginOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + beginTrustedConfirmationOperation(op); + case KMType.VERIFY: + beginSignVerifyOperation(op); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + beginCipherOperation(op); + break; + case KMType.AGREE_KEY: + beginKeyAgreementOperation(op); + break; + default: + KMException.throwIt(KMError.UNIMPLEMENTED); + break; + } + short iv = KMType.INVALID_VALUE; + // If the data[IV] is required to be returned. + // As per VTS, for the decryption operation don't send the iv back. + if (data[IV] != KMType.INVALID_VALUE + && op.getPurpose() != KMType.DECRYPT + && op.getBlockMode() != KMType.ECB) { + iv = KMArray.instance((short) 1); + if (op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) { + // For AES/DES we are generate an random iv of length 16 bytes. + // While sending the iv back for DES/CBC mode of opeation only send + // 8 bytes back. + short ivBlob = KMByteBlob.instance((short) 8); + Util.arrayCopy( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(ivBlob).getBuffer(), + KMByteBlob.cast(ivBlob).getStartOff(), + (short) 8); + data[IV] = ivBlob; + } + KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + } else { + iv = KMArray.instance((short) 0); + } + short macLen = 0; + if (op.getMacLength() != KMType.INVALID_VALUE) { + macLen = (short) (op.getMacLength() / 8); + } + short params = KMKeyParameters.instance(iv); + short resp = KMArray.instance((short) 5); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + KMArray.cast(resp).add((short) 2, data[OP_HANDLE]); + KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode())); + KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen)); + sendOutgoing(apdu, resp); + } + + private void authorizePurpose(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.AES: + case KMType.DES: + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.EC: + if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + if (op.getPurpose() == KMType.ENCRYPT + || op.getPurpose() == KMType.DECRYPT + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.RSA: + if (op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + default: + break; + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, op.getPurpose(), data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + + private void authorizeDigest(KMOperationState op) { + short digests = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]); + op.setDigest(KMType.DIGEST_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(digests).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + op.setDigest((byte) param); + } else if (KMEnumArrayTag.contains( + KMType.PADDING, KMType.RSA_PKCS1_1_5_SIGN, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + short paramPadding = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (paramPadding != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(paramPadding).length() != 1) { + // TODO vts fails because it expects UNSUPPORTED_PADDING_MODE + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + paramPadding = KMEnumArrayTag.cast(paramPadding).get((short) 0); + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if ((paramPadding == KMType.RSA_OAEP || paramPadding == KMType.RSA_PSS) + && param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + case KMType.EC: + case KMType.HMAC: + if ((param == KMType.INVALID_VALUE && op.getPurpose() != KMType.AGREE_KEY) + || !isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + default: + break; + } + } + + private void authorizePadding(KMOperationState op) { + short paddings = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]); + op.setPadding(KMType.PADDING_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(paddings).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) + && param != KMType.PADDING_NONE + && param != KMType.RSA_PSS + && param != KMType.RSA_PKCS1_1_5_SIGN) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) + && param != KMType.PADDING_NONE + && param != KMType.RSA_OAEP + && param != KMType.RSA_PKCS1_1_5_ENCRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + + if (param == KMType.PADDING_NONE && op.getDigest() != KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if ((param == KMType.RSA_OAEP || param == KMType.RSA_PSS) + && op.getDigest() == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || param == KMType.RSA_OAEP) { + // Digest is mandatory in these cases. + if (!isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + } + if (param == KMType.RSA_OAEP) { + short mgfDigest = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[KEY_PARAMETERS]); + if (mgfDigest != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(mgfDigest).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + mgfDigest = KMEnumArrayTag.cast(mgfDigest).get((short) 0); + if (mgfDigest == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + + } else { + mgfDigest = KMType.SHA1; + } + short mgfDigestHwParams = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[HW_PARAMETERS]); + if ((mgfDigestHwParams != KMType.INVALID_VALUE) + && (!KMEnumArrayTag.cast(mgfDigestHwParams).contains(mgfDigest))) { + KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST); + } + if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + op.setMgfDigest((byte) mgfDigest); + } + op.setPadding((byte) param); + break; + case KMType.DES: + case KMType.AES: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + op.setPadding((byte) param); + break; + default: + break; + } + } + + private void authorizeBlockModeAndMacLength(KMOperationState op) { + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + } + if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) { + if (!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + } + short macLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAC_LENGTH, data[KEY_PARAMETERS]); + switch (op.getAlgorithm()) { + case KMType.AES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + case KMType.CTR: + case KMType.GCM: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.GCM) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (macLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + short minMacLen = + KMIntegerTag.getShortValue( + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (macLen % 8 != 0 + || macLen > MAX_GCM_TAG_LENGTH_BITS + || macLen < MIN_GCM_TAG_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + if (param == KMType.CTR) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + break; + case KMType.DES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.HMAC: + short minMacLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + op.setMinMacLength(minMacLen); + if (macLen == KMType.INVALID_VALUE) { + if (op.getPurpose() == KMType.SIGN) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + } else { + // MAC length may not be specified for verify. + if (op.getPurpose() == KMType.VERIFY) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (macLen % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + data[HW_TOKEN] = KMType.INVALID_VALUE; + } + authorizeUserSecureIdAuthTimeout(op, scratchPad); + authorizeDeviceUnlock(scratchPad); + authorizeKeyUsageForCount(scratchPad); + + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB); + + // Validate early boot + // VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is + // present + if (kmDataStore.getEarlyBootEndedStatus()) { + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + } + + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail if it is not a Decrypt operation + data[IV] = KMType.INVALID_VALUE; + + if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE) + && KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE) + && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + + short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + // If Nonce is present then check whether the size of nonce is correct. + if (nonce != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(nonce).getValue(); + // For CBC mode - iv must be 8 bytes + if (op.getBlockMode() == KMType.CBC + && op.getAlgorithm() == KMType.DES + && KMByteBlob.cast(data[IV]).length() != 8) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For GCM mode - IV must be 12 bytes + if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For AES CBC and CTR modes IV must be 16 bytes + if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR) + && op.getAlgorithm() == KMType.AES + && KMByteBlob.cast(data[IV]).length() != 16) { + KMException.throwIt(KMError.INVALID_NONCE); + } + } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { + + // For symmetric decryption iv is required + if (op.getPurpose() == KMType.DECRYPT + && (op.getBlockMode() == KMType.CBC + || op.getBlockMode() == KMType.GCM + || op.getBlockMode() == KMType.CTR)) { + KMException.throwIt(KMError.MISSING_NONCE); + } else if (op.getBlockMode() == KMType.ECB) { + // For ECB we create zero length nonce + data[IV] = KMByteBlob.instance((short) 0); + } else if (op.getPurpose() == KMType.ENCRYPT) { + + // For encrypt mode if nonce is absent then create random nonce of correct length + byte ivLen = 16; + if (op.getBlockMode() == KMType.GCM) { + ivLen = 12; + } else if (op.getAlgorithm() == KMType.DES) { + ivLen = 8; + } + data[IV] = KMByteBlob.instance(ivLen); + seProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length()); + } + } + } + + private void beginKeyAgreementOperation(KMOperationState op) { + if (op.getAlgorithm() != KMType.EC) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF1 Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } + + private void beginCipherOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.DECRYPT) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + (byte) op.getMgfDigest(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.AES: + case KMType.DES: + if (op.getBlockMode() == KMType.GCM) { + op.setAesGcmUpdateStart(); + } + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length(), + op.getMacLength())); + } catch (CryptoException exception) { + if (exception.getReason() == CryptoException.ILLEGAL_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } else if (exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + } + + private void beginTrustedConfirmationOperation(KMOperationState op) { + // Check for trusted confirmation - if required then set the signer in op state. + if (KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS]) + != KMType.INVALID_VALUE) { + + op.setTrustedConfirmationSigner( + seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey())); + + op.getTrustedConfirmationSigner() + .update(confirmationToken, (short) 0, (short) confirmationToken.length); + } + } + + private void beginSignVerifyOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.EC: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. So this is the reason we are using KMType.SIGN directly + // instead of using op.getPurpose(). + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) KMType.SIGN, + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0, + (short) 0)); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + } + + private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) { + short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); + if (!KMInteger.cast(secureUserId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) { + return true; + } + } + + short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); + if (!KMInteger.cast(authenticatorId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) { + return true; + } + } + return false; + } + + private boolean authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + return false; + } + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { + return false; + } + // check auth type + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) authType) == 0) { + return false; + } + return true; + } + + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { + short authTime; + short authType; + // Authorize User Secure Id and Auth timeout + short userSecureIdPtr = + KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + if (userSecureIdPtr != KMType.INVALID_VALUE) { + // Authentication required. + if (KMType.INVALID_VALUE + != KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // authenticator type must be provided. + if (KMType.INVALID_VALUE + == (authType = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { + // authenticate user + if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + authTimeoutTagPtr = + KMKeyParameters.findTag( + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]); + if (authTimeoutTagPtr == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); + // set the one time auth + op.setOneTimeAuthReqd(true); + // set the authentication time stamp in operation state + authTime = + addIntegers( + authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); + op.setAuthTime( + KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + // auth time validation will happen in update or finish + op.setAuthTimeoutValidated(false); + } else { + // auth per operation required + // store user secure id and authType in OperationState. + op.setUserSecureId(userSecureIdPtr); + op.setAuthType((byte) authType); + // set flags + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId, authenticatorType and timestamp + // are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId values are in little endian order, + // but authenticatorType and timestamp are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return false; + } + if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) { + return verifyHwTokenMacInLittleEndian(hwToken, scratchPad); + } else { + return verifyHwTokenMacInBigEndian(hwToken, scratchPad); + } + } + + private short importKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key + KMArray.cast(cmd).add((short) 4, params); // attest key params + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processImportKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = importKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5); + keyFmt = KMEnum.cast(keyFmt).getVal(); + + data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty. + data[ORIGIN] = KMType.IMPORTED; + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + importKey(apdu, keyFmt, scratchPad); + } + + private void validateImportKey(short params, short keyFmt) { + short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, params); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // Rollback protection not supported + KMTag.assertAbsence( + params, + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + // As per specification, Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is + // provided to IKeyMintDevice::importKey + KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(params)) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + // Algorithm must be present + KMTag.assertPresence(params, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + // key format must be raw if aes, des or hmac and pkcs8 for rsa and ec. + if ((alg == KMType.AES || alg == KMType.DES || alg == KMType.HMAC) && keyFmt != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + if ((alg == KMType.RSA || alg == KMType.EC) && keyFmt != KMType.PKCS8) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + } + + private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { + validateImportKey(data[KEY_PARAMETERS], keyFmt); + // Check algorithm and dispatch to appropriate handler. + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + switch (alg) { + case KMType.RSA: + importRSAKey(scratchPad); + break; + case KMType.AES: + importAESKey(scratchPad); + break; + case KMType.DES: + importTDESKey(scratchPad); + break; + case KMType.HMAC: + importHmacKey(scratchPad); + break; + case KMType.EC: + importECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + makeKeyCharacteristics(scratchPad); + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private void importECKeys(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyBlob = pkcs8.decodeEc(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyBlob).get((short) 0); + data[SECRET] = KMArray.cast(keyBlob).get((short) 1); + // initialize 256 bit p256 key for given private key and public key. + short index = 0; + // check whether the key size tag is present in key parameters. + short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8); + short keySize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keySize != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ keySize == 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (keySize != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keySize = KMInteger.uint_16((short) 256); + keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keySize); + Util.setShort(scratchPad, index, keySize); + index += 2; + } + // check the curve if present in key parameters. + short curve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if (curve != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret length for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ curve == KMType.P_256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (curve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the curve to scratchPad + curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + Util.setShort(scratchPad, index, curve); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.EC, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // add scratch pad to key parameters + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void importHmacKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create HMAC key of up to 512 bit + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + keysize = KMInteger.uint_16(keysize); + short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keySizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.HMAC, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate HMAC Key parameters + validateHmacKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importTDESKey(byte[] scratchPad) { + // Decode Key Material + data[SECRET] = data[IMPORTED_KEY_BLOB]; + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 168) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (192 != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (keysize != 192) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 168); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.DES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + // update the key parameters list + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void validateAesKeySize(short keySizeBits) { + if (keySizeBits != 128 && keySizeBits != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private void importAESKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create 128 or 256 bit AES key + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + validateAesKeySize(keysize); + } else { + // add the key size to scratchPad + keysize = (short) (8 * KMByteBlob.cast(data[SECRET]).length()); + validateAesKeySize(keysize); + keysize = KMInteger.uint_16(keysize); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.AES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate AES Key parameters + validateAESKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importRSAKey(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyblob = pkcs8.decodeRsa(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyblob).get((short) 0); + short pubKeyExp = KMArray.cast(keyblob).get((short) 1); + data[SECRET] = KMArray.cast(keyblob).get((short) 2); + if (F4.length != KMByteBlob.cast(pubKeyExp).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (Util.arrayCompare( + F4, + (short) 0, + KMByteBlob.cast(pubKeyExp).getBuffer(), + KMByteBlob.cast(pubKeyExp).getStartOff(), + (short) F4.length) + != 0) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index in scratchPad for update parameters. + // validate public exponent if present in key params - it must be 0x010001 + short len = + KMIntegerTag.getValue( + scratchPad, + (short) 10, // using offset 10 as first 10 bytes reserved for update params + KMType.ULONG_TAG, + KMType.RSA_PUBLIC_EXPONENT, + data[KEY_PARAMETERS]); + if (len != KMTag.INVALID_VALUE) { + if (len != 4 + || Util.getShort(scratchPad, (short) 10) != 0x01 + || Util.getShort(scratchPad, (short) 12) != 0x01) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add public exponent to scratchPad + Util.setShort(scratchPad, (short) 10, (short) 0x01); + Util.setShort(scratchPad, (short) 12, (short) 0x01); + pubKeyExp = KMInteger.uint_32(scratchPad, (short) 10); + pubKeyExp = KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, pubKeyExp); + Util.setShort(scratchPad, index, pubKeyExp); + index += 2; + } + + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short kSize = (short) (KMByteBlob.length(data[PUB_KEY]) * 8); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 2048 || (keysize != kSize)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + if (2048 != kSize) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 2048); + keysize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysize); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate RSA Key parameters + validateRSAKey(scratchPad); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void updateKeyParameters(byte[] newParams, short len) { + if (len == 0) { + return; // nothing to update + } + // Create Update Param array and copy current params + short params = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals(); + len = (short) (KMArray.cast(params).length() + (short) (len / 2)); + short updatedParams = KMArray.instance(len); // update params + + len = KMArray.cast(params).length(); + short index = 0; + + // copy the existing key parameters to updated array + while (index < len) { + short tag = KMArray.cast(params).get(index); + KMArray.cast(updatedParams).add(index, tag); + index++; + } + + // copy new parameters to updated array + len = KMArray.cast(updatedParams).length(); + short newParamIndex = 0; // index in ptrArr + while (index < len) { + short tag = Util.getShort(newParams, newParamIndex); + KMArray.cast(updatedParams).add(index, tag); + index++; + newParamIndex += 2; + } + // replace with updated key parameters. + data[KEY_PARAMETERS] = KMKeyParameters.instance(updatedParams); + } + + private short initStrongBoxCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // OS version + KMArray.cast(cmd).add((short) 1, KMInteger.exp()); // OS patch level + KMArray.cast(cmd).add((short) 2, KMInteger.exp()); // Vendor patch level + return receiveIncoming(apdu, cmd); + } + + // This command is executed to set the boot parameters. + // releaseAllOperations has to be called on every boot, so + // it is called from inside initStrongBoxCmd. Later in future if + // initStrongBoxCmd is removed, then make sure that releaseAllOperations + // is moved to a place where it is called on every boot. + private void processInitStrongBoxCmd(APDU apdu) { + short cmd = initStrongBoxCmd(apdu); + + short osVersion = KMArray.cast(cmd).get((short) 0); + short osPatchLevel = KMArray.cast(cmd).get((short) 1); + short vendorPatchLevel = KMArray.cast(cmd).get((short) 2); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS); + } + + public void reboot() { + // flag to maintain early boot ended state + kmDataStore.setEarlyBootEndedStatus(false); + // Clear all the operation state. + releaseAllOperations(); + // Hmac is cleared, so generate a new Hmac nonce. + initHmacNonceAndSeed(); + // Clear all auth tags. + kmDataStore.removeAllAuthTags(); + } + + protected void initSystemBootParams( + short osVersion, short osPatchLevel, short vendorPatchLevel, short bootPatchLevel) { + osVersion = KMInteger.uint_16(osVersion); + osPatchLevel = KMInteger.uint_16(osPatchLevel); + vendorPatchLevel = KMInteger.uint_16((short) vendorPatchLevel); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + } + + protected void setOsVersion(short version) { + kmDataStore.setOsVersion( + KMInteger.cast(version).getBuffer(), + KMInteger.cast(version).getStartOff(), + KMInteger.cast(version).length()); + } + + protected void setOsPatchLevel(short patch) { + kmDataStore.setOsPatch( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + protected void setVendorPatchLevel(short patch) { + kmDataStore.setVendorPatchLevel( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + private short generateKeyCmd(APDU apdu) { + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, params); // key params + KMArray.cast(cmd).add((short) 1, blob); // attest key + KMArray.cast(cmd).add((short) 2, params); // attest key params + KMArray.cast(cmd).add((short) 3, blob); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processGenerateKey(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = generateKeyCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3); + data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty. + // ROLLBACK_RESISTANCE not supported. + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + + // Algorithm must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + // Check algorithm and dispatch to appropriate handler. + switch (alg) { + case KMType.RSA: + generateRSAKey(scratchPad); + break; + case KMType.AES: + generateAESKey(scratchPad); + break; + case KMType.DES: + generateTDESKey(scratchPad); + break; + case KMType.HMAC: + generateHmacKey(scratchPad); + break; + case KMType.EC: + generateECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + // create key blob and associated attestation. + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + // construct the certificate and place the encoded data in data[CERTIFICATE] + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private short getApplicationId(short params) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, params); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appId; + } + + private short getApplicationData(short params) { + short appData = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, params); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appData; + } + + private short getAttestationMode(short attKeyBlob, short attChallenge) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + short mode = KMType.NO_CERT; + if (KMEnumTag.cast(alg).getValue() != KMType.RSA + && KMEnumTag.cast(alg).getValue() != KMType.EC) { + return mode; + } + // If attestation keyblob preset + if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { + // No attestation challenge present then it is an error + if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { + KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING); + } else { + mode = KMType.ATTESTATION_CERT; + } + } else { // no attestation key blob + // Attestation challenge present then it is an error because no factory provisioned attest key + if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } else if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS]) + || KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) { + // The Purpose value can be read from either data[HW_PARAMETERS] or data[KEY_PARAMETERS] + // as the values will be same, and they are cryptographically bound. + mode = KMType.SELF_SIGNED_CERT; + } else { + mode = KMType.FAKE_CERT; + } + } + return mode; + } + + private KMAttestationCert generateAttestation( + short attKeyBlob, short attKeyParam, byte[] scratchPad) { + // 1) If attestation key is present and attestation challenge is absent then it is an error. + // 2) If attestation key is absent and attestation challenge is present then it is an error as + // factory provisioned attestation key is not supported. + // 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it + // is an error. + // 4) If the generated/imported keys are RSA or EC then validity period must be specified. + // Device Unique Attestation is not supported. + short heapStart = repository.getHeapIndex(); + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.DEVICE_UNIQUE_ATTESTATION, + KMError.CANNOT_ATTEST_IDS); + // Read attestation challenge if present + short attChallenge = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]); + if (attChallenge != KMType.INVALID_VALUE) { + attChallenge = KMByteTag.cast(attChallenge).getValue(); + } + // No attestation required for symmetric keys + short mode = getAttestationMode(attKeyBlob, attChallenge); + KMAttestationCert cert = null; + + switch (mode) { + case KMType.ATTESTATION_CERT: + cert = + makeAttestationCert( + attKeyBlob, attKeyParam, attChallenge, data[ATTEST_KEY_ISSUER], scratchPad); + break; + case KMType.SELF_SIGNED_CERT: + cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY], mode, scratchPad); + break; + case KMType.FAKE_CERT: + // Generate certificate with no signature. + cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], mode, scratchPad); + break; + default: + data[CERTIFICATE] = KMType.INVALID_VALUE; + return null; + } + // Certificate Data is converted to cbor and written to the end of the stack. + short certData = repository.allocReclaimableMemory(MAX_CERT_SIZE); + // Leave first 4 bytes for Array header and ByteBlob header. + cert.buffer(repository.getHeap(), (short) (certData + 4), (short) (MAX_CERT_SIZE - 4)); + // Build the certificate - this will sign the cert + cert.build(); + // Certificate is now built so the data in the heap starting from heapStart to the current + // heap index can be reused. So resetting the heap index to heapStart. + repository.setHeapIndex(heapStart); + data[CERTIFICATE] = certData; + return cert; + } + + // Encodes KeyCharacteristics at the end of the heap + private void encodeKeyCharacteristics(short keyChars) { + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short ptr = repository.allocReclaimableMemory(MAX_KEY_CHARS_SIZE); + short len = encoder.encode(keyChars, buffer, ptr, prevReclaimIndex, MAX_KEY_CHARS_SIZE); + // shift the encoded KeyCharacteristics data towards the right till the data[CERTIFICATE] + // offset. + Util.arrayCopyNonAtomic(buffer, ptr, buffer, (short) (ptr + (MAX_KEY_CHARS_SIZE - len)), len); + // Reclaim the unused memory. + repository.reclaimMemory((short) (MAX_KEY_CHARS_SIZE - len)); + } + + // Encodes KeyBlob at the end of the heap + private void encodeKeyBlob(short keyBlobPtr) { + // allocate reclaimable memory. + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short top = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + short keyBlob = encoder.encode(keyBlobPtr, buffer, top, prevReclaimIndex, MAX_KEYBLOB_SIZE); + Util.arrayCopyNonAtomic( + repository.getHeap(), + top, + repository.getHeap(), + (short) (top + MAX_KEYBLOB_SIZE - keyBlob), + keyBlob); + short newTop = (short) (top + MAX_KEYBLOB_SIZE - keyBlob); + // Encode the KeyBlob array inside a ByteString. Get the length of + // the ByteString header. + short encodedBytesLength = encoder.getEncodedBytesLength(keyBlob); + newTop -= encodedBytesLength; + encoder.encodeByteBlobHeader(keyBlob, buffer, newTop, encodedBytesLength); + // Reclaim unused memory. + repository.reclaimMemory((short) (newTop - top)); + } + + private short readKeyBlobVersion(short keyBlob) { + short version = KMType.INVALID_VALUE; + try { + version = + decoder.readKeyblobVersion( + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + if (version == KMType.INVALID_VALUE) { + // If Version is not present. Then it is either an old KeyBlob or + // corrupted KeyBlob. + version = 0; + } else { + version = KMInteger.cast(version).getShort(); + if (version > KEYBLOB_CURRENT_VERSION || version < 0) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return version; + } + + private void readKeyBlobParams(short version, short parsedKeyBlob) { + data[KEY_BLOB] = parsedKeyBlob; + // initialize data + switch (version) { + case (short) 0: + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V0) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 4); + } + // Set the data[KEY_BLOB_VERSION_DATA_OFFSET] with integer value of 0 so + // that it will used at later point of time. + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_8((byte) 0); + break; + case (short) 1: + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 4); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V1) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 5); + } + break; + case (short) 2: + case (short) 3: + data[SECRET] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_SECRET); + data[NONCE] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_NONCE); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_AUTH_TAG); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PARAMS); + data[KEY_BLOB_VERSION_DATA_OFFSET] = + KMArray.cast(parsedKeyBlob).get(KEY_BLOB_VERSION_OFFSET); + data[CUSTOM_TAGS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_CUSTOM_TAGS); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V2_V3) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PUB_KEY); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + // Decode the KeyBlob from CBOR structures to the sub types of KMType. + private void decodeKeyBlob(short version, short keyBlob) { + // Decode KeyBlob and read the KeyBlob params based on the version. + short parsedBlob = + decoder.decodeArray( + createKeyBlobExp(version), + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + short minArraySize = 0; + switch (version) { + case 0: + minArraySize = SYM_KEY_BLOB_SIZE_V0; + break; + case 1: + minArraySize = SYM_KEY_BLOB_SIZE_V1; + break; + case 2: + case 3: + minArraySize = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + // KeyBlob size should not be less than the minimum KeyBlob size. + if (KMArray.cast(parsedBlob).length() < minArraySize) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + readKeyBlobParams(version, parsedBlob); + } + + // Decrypts the secret key in the KeyBlob. The secret can be a Symmetric or Asymmetric key. + private void processDecryptSecret(short version, short appId, short appData, byte[] scratchPad) { + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + + data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, data[ROT], scratchPad); + // Decrypt Secret and verify auth tag + decryptSecret(scratchPad, version); + short keyBlobSecretOff = 0; + switch (version) { + case 0: + // V0 KeyBlob + // KEY_BLOB = [ + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 0; + break; + case 1: + // V1 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 1; + break; + case 2: + case 3: + // V2 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // CUSTOM_TAGS, + // PUBKEY + // ] + keyBlobSecretOff = KEY_BLOB_SECRET; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + KMArray.cast(data[KEY_BLOB]).add(keyBlobSecretOff, data[SECRET]); + } + + private void parseEncryptedKeyBlob( + short keyBlob, short appId, short appData, byte[] scratchPad, short version) { + // make root of trust blob + data[ROT] = readROT(scratchPad, version); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + try { + decodeKeyBlob(version, keyBlob); + processDecryptSecret(version, appId, appData, scratchPad); + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + private void decryptSecret(byte[] scratchPad, short version) { + // derive master key - stored in derivedKey + short len; + short authDataOff = 0; + short authDataLen = 0; + byte[] authDataBuff = null; + switch (version) { + case 3: + len = deriveKey(scratchPad); + break; + + case 2: + case 1: + case 0: + makeAuthData(version, scratchPad); + len = deriveKeyForOldKeyBlobs(scratchPad); + authDataBuff = repository.getHeap(); + authDataOff = data[AUTH_DATA]; + authDataLen = data[AUTH_DATA_LENGTH]; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + authDataBuff, + authDataOff, + authDataLen, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Copy the decrypted secret + data[SECRET] = + KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); + } + + private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // Copy timestamp to scratchpad + Util.arrayCopyNonAtomic( + KMInteger.cast(timeStamp).getBuffer(), + KMInteger.cast(timeStamp).getStartOff(), + scratchPad, + (short) (16 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // add authTime in millis to timestamp. + KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); + return KMInteger.uint_64(scratchPad, (short) 16); + } + + public void powerReset() { + // TODO handle power reset signal. + releaseAllOperations(); + resetWrappingKey(); + } + + private void updateTrustedConfirmationOperation(KMOperationState op) { + if (op.isTrustedConfirmationRequired()) { + op.getTrustedConfirmationSigner() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } + } + + private void finishTrustedConfirmationOperation(KMOperationState op) { + // Perform trusted confirmation if required + if (op.isTrustedConfirmationRequired()) { + if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + + boolean verified = + op.getTrustedConfirmationSigner() + .verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getBuffer(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()); + if (!verified) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + } + } + + private boolean isDigestSupported(short alg, short digest) { + switch (alg) { + case KMType.RSA: + case KMType.EC: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + default: + break; + } + return true; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java new file mode 100644 index 0000000..65117eb --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -0,0 +1,1075 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.seprovider.KMUpgradable; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import org.globalplatform.upgrade.Element; + +/** + * This is a storage class which helps in storing the provisioned data, ROT, OS version, Boot patch + * level, Vendor Patchlevel, HMAC nonce, computed shared secret, 8 auth tags, device-locked, + * device-locked timestamp and device-locked password only. Only the provisioned data is restored + * back during applet upgrades and the remaining data is flushed. + */ +public class KMKeymintDataStore implements KMUpgradable { + + // Data table configuration + public static final short KM_APPLET_PACKAGE_VERSION_1 = 0x0100; + public static final short KM_APPLET_PACKAGE_VERSION_2 = 0x0200; + public static final short KM_APPLET_PACKAGE_VERSION_3 = 0x0300; + public static final short KM_APPLET_PACKAGE_VERSION_4 = 0x0400; + public static final byte DATA_INDEX_SIZE = 17; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_LENGTH = 0; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + public static final short DATA_MEM_SIZE = 300; + // Data table offsets + public static final byte HMAC_NONCE = 0; + public static final byte BOOT_OS_VERSION = 1; + public static final byte BOOT_OS_PATCH_LEVEL = 2; + public static final byte VENDOR_PATCH_LEVEL = 3; + public static final byte DEVICE_LOCKED_TIME = 4; + public static final byte DEVICE_LOCKED = 5; + public static final byte DEVICE_LOCKED_PASSWORD_ONLY = 6; + // Total 8 auth tags, so the next offset is AUTH_TAG_1 + 8 + public static final byte AUTH_TAG_1 = 7; + public static final byte DEVICE_STATUS_FLAG = 15; + public static final byte EARLY_BOOT_ENDED_FLAG = 16; + // Data Item sizes + public static final byte HMAC_SEED_NONCE_SIZE = 32; + public static final byte COMPUTED_HMAC_KEY_SIZE = 32; + public static final byte OS_VERSION_SIZE = 4; + public static final byte OS_PATCH_SIZE = 4; + public static final byte VENDOR_PATCH_SIZE = 4; + public static final byte DEVICE_LOCK_TS_SIZE = 8; + public static final byte MAX_BLOB_STORAGE = 8; + public static final byte AUTH_TAG_LENGTH = 16; + public static final byte AUTH_TAG_COUNTER_SIZE = 4; + public static final byte AUTH_TAG_ENTRY_SIZE = (AUTH_TAG_LENGTH + AUTH_TAG_COUNTER_SIZE + 1); + // Device boot states. Applet starts executing the + // core commands once all the states are set. The commands + // that are allowed irrespective of these states are: + // All the provision commands + // INS_GET_HW_INFO_CMD + // INS_ADD_RNG_ENTROPY_CMD + // INS_COMPUTE_SHARED_HMAC_CMD + // INS_GET_HMAC_SHARING_PARAM_CMD + public static final byte SET_BOOT_PARAMS_SUCCESS = 0x01; + public static final byte SET_SYSTEM_PROPERTIES_SUCCESS = 0x02; + public static final byte NEGOTIATED_SHARED_SECRET_SUCCESS = 0x04; + // Old Data table offsets + private static final byte OLD_PROVISIONED_STATUS_OFFSET = 18; + private static final byte SHARED_SECRET_KEY_SIZE = 32; + private static final byte DEVICE_STATUS_FLAG_SIZE = 1; + private static final short UDS_CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. + private static final short DICE_CERT_CHAIN_MAX_SIZE = 512; + private static KMKeymintDataStore kmDataStore; + // Secure Boot Mode + public byte secureBootMode; + // Data - originally was in repository + private byte[] attIdBrand; + private byte[] attIdDevice; + private byte[] attIdProduct; + private byte[] attIdSerial; + private byte[] attIdImei; + private byte[] attIdSecondImei; + private byte[] attIdMeId; + private byte[] attIdManufacturer; + private byte[] attIdModel; + // Boot parameters + private byte[] verifiedHash; + private byte[] bootKey; + private byte[] bootPatchLevel; + private boolean deviceBootLocked; + private short bootState; + // Challenge for Root of trust + private byte[] challenge; + + /* + * Applets upgrading to KeyMint3.0 may not have the second imei provisioned. + * So this flag is used to ignore the SECOND_IMEI tag if the previous Applet's + * KeyMint version is less than 3.0. + */ + public boolean ignoreSecondImei; + private short dataIndex; + private byte[] dataTable; + private KMSEProvider seProvider; + private KMRepository repository; + private byte[] udsCertChain; + private byte[] diceCertChain; + private KMKey masterKey; + private KMKey deviceUniqueKeyPair; + private KMKey preSharedKey; + private KMKey computedHmacKey; + private KMKey rkpMacKey; + private byte[] oemRootPublicKey; + private short provisionStatus; + + public KMKeymintDataStore(KMSEProvider provider, KMRepository repo) { + seProvider = provider; + repository = repo; + boolean isUpgrading = provider.isUpgrading(); + initDataTable(); + // Initialize the device locked status + if (!isUpgrading) { + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + diceCertChain = new byte[DICE_CERT_CHAIN_MAX_SIZE]; + oemRootPublicKey = new byte[65]; + } + setDeviceLockPasswordOnly(false); + setDeviceLock(false); + kmDataStore = this; + } + + public static KMKeymintDataStore instance() { + return kmDataStore; + } + + private void initDataTable() { + if (dataTable == null) { + dataTable = new byte[DATA_MEM_SIZE]; + dataIndex = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + } + + private short dataAlloc(short length) { + if (((short) (dataIndex + length)) > dataTable.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex += length; + return (short) (dataIndex - length); + } + + private void clearDataEntry(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen != 0) { + short dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayFillNonAtomic(dataTable, dataPtr, dataLen, (byte) 0); + JCSystem.commitTransaction(); + } + } + + private void writeDataEntry(short id, byte[] buf, short offset, short len) { + short dataPtr; + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen == 0) { + dataPtr = dataAlloc(len); + JCSystem.beginTransaction(); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET), dataPtr); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH), len); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } else { + if (len != dataLen) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } + } + + private short readDataEntry(short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short readDataEntry(byte[] dataTable, short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short dataLength(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + } + + public short readData(short id) { + short len = dataLength(id); + if (len != 0) { + short blob = KMByteBlob.instance(dataLength(id)); + readDataEntry(id, KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + return blob; + } + return KMType.INVALID_VALUE; + } + + public short getHmacNonce() { + return readData(HMAC_NONCE); + } + + public short getOsVersion() { + short blob = readData(BOOT_OS_VERSION); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getVendorPatchLevel() { + short blob = readData(VENDOR_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getOsPatch() { + short blob = readData(BOOT_OS_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + private boolean readBoolean(short id) { + short blob = readData(id); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return (byte) ((repository.getHeap())[KMByteBlob.cast(blob).getStartOff()]) == 0x01; + } + + public boolean getDeviceLock() { + return readBoolean(DEVICE_LOCKED); + } + + public void setDeviceLock(boolean flag) { + writeBoolean(DEVICE_LOCKED, flag); + } + + public boolean getDeviceLockPasswordOnly() { + return readBoolean(DEVICE_LOCKED_PASSWORD_ONLY); + } + + public void setDeviceLockPasswordOnly(boolean flag) { + writeBoolean(DEVICE_LOCKED_PASSWORD_ONLY, flag); + } + + public boolean getEarlyBootEndedStatus() { + return readBoolean(EARLY_BOOT_ENDED_FLAG); + } + + public void setEarlyBootEndedStatus(boolean flag) { + writeBoolean(EARLY_BOOT_ENDED_FLAG, flag); + } + + public short getDeviceTimeStamp() { + short blob = readData(DEVICE_LOCKED_TIME); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_64( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public void setOsVersion(byte[] buf, short start, short len) { + if (len != OS_VERSION_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_VERSION, buf, start, len); + } + + public void setVendorPatchLevel(byte[] buf, short start, short len) { + if (len != VENDOR_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(VENDOR_PATCH_LEVEL, buf, start, len); + } + + private void writeBoolean(short id, boolean flag) { + short start = repository.alloc((short) 1); + if (flag) { + (repository.getHeap())[start] = (byte) 0x01; + } else { + (repository.getHeap())[start] = (byte) 0x00; + } + writeDataEntry(id, repository.getHeap(), start, (short) 1); + } + + public void setDeviceLockTimestamp(byte[] buf, short start, short len) { + if (len != DEVICE_LOCK_TS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(DEVICE_LOCKED_TIME, buf, start, len); + } + + public void clearDeviceBootStatus() { + clearDataEntry(DEVICE_STATUS_FLAG); + } + + public void setDeviceBootStatus(byte initStatus) { + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + buf[offset] |= initStatus; + writeDataEntry(DEVICE_STATUS_FLAG, buf, offset, DEVICE_STATUS_FLAG_SIZE); + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + } + + public boolean isDeviceReady() { + boolean result = false; + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + byte bootCompleteStatus = + (SET_BOOT_PARAMS_SUCCESS + | SET_SYSTEM_PROPERTIES_SUCCESS + | NEGOTIATED_SHARED_SECRET_SUCCESS); + if (bootCompleteStatus == (buf[offset] & bootCompleteStatus)) { + result = true; + } + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + return result; + } + + public short getDeviceBootStatus(byte[] scratchpad, short offset) { + scratchpad[offset] = 0; + return readDataEntry(DEVICE_STATUS_FLAG, scratchpad, offset); + } + + public void clearDeviceLockTimeStamp() { + clearDataEntry(DEVICE_LOCKED_TIME); + } + + public void setOsPatch(byte[] buf, short start, short len) { + if (len != OS_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_PATCH_LEVEL, buf, start, len); + } + + private boolean isAuthTagSlotAvailable(short tagId, byte[] buf, short offset) { + readDataEntry(tagId, buf, offset); + return (0 == buf[offset]); + } + + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (len != HMAC_SEED_NONCE_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(HMAC_NONCE, nonce, offset, len); + } + + public void clearHmacNonce() { + clearDataEntry(HMAC_NONCE); + } + + public boolean persistAuthTag(short authTag) { + + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + short authTagEntry = repository.alloc(AUTH_TAG_ENTRY_SIZE); + short scratchPadOff = repository.alloc(AUTH_TAG_ENTRY_SIZE); + byte[] scratchPad = repository.getHeap(); + writeAuthTagState(repository.getHeap(), authTagEntry, (byte) 1); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + repository.getHeap(), + (short) (authTagEntry + 1), + AUTH_TAG_LENGTH); + Util.setShort( + repository.getHeap(), (short) (authTagEntry + AUTH_TAG_LENGTH + 1 + 2), (short) 1); + short index = 0; + while (index < MAX_BLOB_STORAGE) { + if ((dataLength((short) (index + AUTH_TAG_1)) == 0) + || isAuthTagSlotAvailable((short) (index + AUTH_TAG_1), scratchPad, scratchPadOff)) { + + writeDataEntry( + (short) (index + AUTH_TAG_1), repository.getHeap(), authTagEntry, AUTH_TAG_ENTRY_SIZE); + return true; + } + index++; + } + return false; + } + + public void removeAllAuthTags() { + short index = 0; + while (index < MAX_BLOB_STORAGE) { + clearDataEntry((short) (index + AUTH_TAG_1)); + index++; + } + } + + public boolean isAuthTagPersisted(short authTag) { + return (KMType.INVALID_VALUE != findTag(authTag)); + } + + private short findTag(short authTag) { + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short index = 0; + short found; + short offset = repository.alloc(AUTH_TAG_ENTRY_SIZE); + while (index < MAX_BLOB_STORAGE) { + if (dataLength((short) (index + AUTH_TAG_1)) != 0) { + readDataEntry((short) (index + AUTH_TAG_1), repository.getHeap(), offset); + found = + Util.arrayCompare( + repository.getHeap(), + (short) (offset + 1), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + AUTH_TAG_LENGTH); + if (found == 0) { + return (short) (index + AUTH_TAG_1); + } + } + index++; + } + return KMType.INVALID_VALUE; + } + + public short getRateLimitedKeyCount(short authTag, byte[] out, short outOff) { + short tag = findTag(authTag); + short blob; + if (tag != KMType.INVALID_VALUE) { + blob = readData(tag); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(blob).getBuffer(), + (short) (KMByteBlob.cast(blob).getStartOff() + AUTH_TAG_LENGTH + 1), + out, + outOff, + AUTH_TAG_COUNTER_SIZE); + return AUTH_TAG_COUNTER_SIZE; + } + return (short) 0; + } + + public void setRateLimitedKeyCount(short authTag, byte[] buf, short off, short len) { + short tag = findTag(authTag); + if (tag != KMType.INVALID_VALUE) { + short dataPtr = readData(tag); + Util.arrayCopyNonAtomic( + buf, + off, + KMByteBlob.cast(dataPtr).getBuffer(), + (short) (KMByteBlob.cast(dataPtr).getStartOff() + AUTH_TAG_LENGTH + 1), + len); + writeDataEntry( + tag, + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + KMByteBlob.cast(dataPtr).length()); + } + } + + public void persistUdsCertChain(byte[] buf, short offset, short len) { + // Input buffer contains encoded Uds certificate chain as shown below. + // UdsDKSignatures = { + // + SignerName => DKCertChain + // } + // SignerName = tstr + // DKCertChain = [ + // 2* Certificate // Root -> Leaf. Root is the vendo r + // // self-signed cert, leaf contains DK_pu b + // ] + // Certificate = COSE_Sign1 of a public key + if ((short) (len + 2) > UDS_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(udsCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, udsCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + public short getUdsCertChainLength() { + return Util.getShort(udsCertChain, (short) 0); + } + + public byte[] getUdsCertChain() { + return udsCertChain; + } + + public byte[] getDiceCertificateChain() { + return diceCertChain; + } + + public void persistBootCertificateChain(byte[] buf, short offset, short len) { + if ((short) (len + 2) > DICE_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(diceCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, diceCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + private void writeAuthTagState(byte[] buf, short offset, byte state) { + buf[offset] = state; + } + + // The master key should only be generated during applet installation and + // during a device factory reset event. + public KMKey createMasterKey(short keySizeBits) { + if (masterKey == null) { + masterKey = seProvider.createMasterKey(masterKey, keySizeBits); + } + return (KMKey) masterKey; + } + + public KMKey regenerateMasterKey() { + return seProvider.createMasterKey(masterKey, KMKeymasterApplet.MASTER_KEY_SIZE); + } + + public KMKey getMasterKey() { + return masterKey; + } + + public void createPresharedKey(byte[] keyData, short offset, short length) { + if (length != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (preSharedKey == null) { + preSharedKey = seProvider.createPreSharedKey(preSharedKey, keyData, offset, length); + } + } + + public KMKey getPresharedKey() { + if (preSharedKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return preSharedKey; + } + + public void createComputedHmacKey(byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (computedHmacKey == null) { + computedHmacKey = seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } else { + seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } + } + + public KMKey getComputedHmacKey() { + if (computedHmacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return computedHmacKey; + } + + public KMKey createRkpDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (deviceUniqueKeyPair == null) { + deviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return deviceUniqueKeyPair; + } + + public KMKey getRkpDeviceUniqueKeyPair() { + return ((KMKey) deviceUniqueKeyPair); + } + + public void createRkpMacKey(byte[] keydata, short offset, short length) { + if (rkpMacKey == null) { + rkpMacKey = seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } else { + seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } + } + + public KMKey getRkpMacKey() { + if (rkpMacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return rkpMacKey; + } + + public short getAttestationId(short tag, byte[] buffer, short start) { + byte[] attestId = null; + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + attestId = attIdBrand; + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + attestId = attIdDevice; + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + attestId = attIdProduct; + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + attestId = attIdSerial; + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + attestId = attIdImei; + break; + // Attestation Id SECOND IMEI + case KMType.ATTESTATION_ID_SECOND_IMEI: + attestId = attIdSecondImei; + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + attestId = attIdMeId; + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + attestId = attIdManufacturer; + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + attestId = attIdModel; + break; + } + if (attestId == null) { + /* 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 && tag == KMType.ATTESTATION_ID_SECOND_IMEI) { + return (short) 0; + } + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + Util.arrayCopyNonAtomic(attestId, (short) 0, buffer, start, (short) attestId.length); + return (short) attestId.length; + } + + public void setAttestationId(short tag, byte[] buffer, short start, short length) { + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + JCSystem.beginTransaction(); + attIdBrand = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdBrand, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + JCSystem.beginTransaction(); + attIdDevice = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdDevice, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + JCSystem.beginTransaction(); + attIdProduct = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdProduct, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + JCSystem.beginTransaction(); + attIdSerial = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSerial, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + JCSystem.beginTransaction(); + attIdImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id SECOND IMEI + case KMType.ATTESTATION_ID_SECOND_IMEI: + JCSystem.beginTransaction(); + attIdSecondImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSecondImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + JCSystem.beginTransaction(); + attIdMeId = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdMeId, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + JCSystem.beginTransaction(); + attIdManufacturer = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdManufacturer, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + JCSystem.beginTransaction(); + attIdModel = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdModel, (short) 0, length); + JCSystem.commitTransaction(); + break; + } + } + + public void deleteAttestationIds() { + attIdBrand = null; + attIdDevice = null; + attIdProduct = null; + attIdSerial = null; + attIdImei = null; + attIdSecondImei = null; + attIdMeId = null; + attIdManufacturer = null; + attIdModel = null; + // Trigger garbage collection. + JCSystem.requestObjectDeletion(); + } + + public short getVerifiedBootHash(byte[] buffer, short start) { + if (verifiedHash == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(verifiedHash, (short) 0, buffer, start, (short) verifiedHash.length); + return (short) verifiedHash.length; + } + + public short getBootKey(byte[] buffer, short start) { + if (bootKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(bootKey, (short) 0, buffer, start, (short) bootKey.length); + return (short) bootKey.length; + } + + public short getBootState() { + return bootState; + } + + public void setBootState(short state) { + bootState = state; + } + + public boolean isDeviceBootLocked() { + return deviceBootLocked; + } + + public short getBootPatchLevel() { + if (bootPatchLevel == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32(bootPatchLevel, (short) 0); + } + + public void setVerifiedBootHash(byte[] buffer, short start, short length) { + if (verifiedHash == null) { + verifiedHash = new byte[32]; + } + if (length != KMKeymasterApplet.VERIFIED_BOOT_HASH_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, verifiedHash, (short) 0, (short) 32); + } + + public void setBootKey(byte[] buffer, short start, short length) { + if (bootKey == null) { + bootKey = new byte[32]; + } + if (length != KMKeymasterApplet.VERIFIED_BOOT_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootKey, (short) 0, (short) 32); + } + + public void setDeviceLocked(boolean state) { + deviceBootLocked = state; + } + + public void setBootPatchLevel(byte[] buffer, short start, short length) { + if (bootPatchLevel == null) { + bootPatchLevel = new byte[4]; + } + if (length > 4 || length < 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootPatchLevel, (short) 0, (short) length); + } + + public void setChallenge(byte[] buf, short start, short length) { + if (challenge == null) { + challenge = new byte[16]; + } + if (length != 16) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(buf, start, challenge, (short) 0, (short) length); + } + + public short getChallenge(byte[] buffer, short start) { + if (challenge == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(challenge, (short) 0, buffer, start, (short) challenge.length); + return (short) challenge.length; + } + + public boolean isProvisionLocked() { + if (0 != (provisionStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + return true; + } + return false; + } + + public short getProvisionStatus() { + return provisionStatus; + } + + public void setProvisionStatus(short pStatus) { + JCSystem.beginTransaction(); + provisionStatus |= pStatus; + JCSystem.commitTransaction(); + } + + public void unlockProvision() { + JCSystem.beginTransaction(); + provisionStatus &= ~KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED; + JCSystem.commitTransaction(); + } + + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (oemRootPublicKey == null) { + oemRootPublicKey = new byte[65]; + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + public byte[] getOEMRootPublicKey() { + if (oemRootPublicKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return oemRootPublicKey; + } + + @Override + public void onSave(Element element) { + // Prmitives + element.write(provisionStatus); + element.write(secureBootMode); + element.write(ignoreSecondImei); + // Objects + element.write(attIdBrand); + element.write(attIdDevice); + element.write(attIdProduct); + element.write(attIdSerial); + element.write(attIdImei); + element.write(attIdSecondImei); + element.write(attIdMeId); + element.write(attIdManufacturer); + element.write(attIdModel); + element.write(udsCertChain); + element.write(diceCertChain); + element.write(oemRootPublicKey); + + // Key Objects + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, masterKey); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY, preSharedKey); + seProvider.onSave( + element, KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR, deviceUniqueKeyPair); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY, rkpMacKey); + } + + @Override + public void onRestore(Element element, short oldVersion, short currentVersion) { + if (oldVersion <= KM_APPLET_PACKAGE_VERSION_1) { + // 1.0 to 4.0 Upgrade happens here. + handlePreviousVersionUpgrade(element); + return; + } else if (oldVersion == KM_APPLET_PACKAGE_VERSION_2) { + handleUpgrade(element, oldVersion); + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 2.0 to 4.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + // Request for ObjectDeletion for unreferenced address of additionalCertChain. + JCSystem.requestObjectDeletion(); + return; + } + handleUpgrade(element, oldVersion); + } + + private void handlePreviousVersionUpgrade(Element element) { + // set ignore Imei flag to true. + ignoreSecondImei = true; + // Read Primitives + // restore old data table index + short oldDataIndex = element.readShort(); + element.readBoolean(); // pop deviceBootLocked + element.readShort(); // pop bootState + + // Read Objects + // restore old data table + byte[] oldDataTable = (byte[]) element.readObject(); + + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + element.readObject(); // pop verifiedHash + element.readObject(); // pop bootKey + element.readObject(); // pop bootPatchLevel + udsCertChain = (byte[]) element.readObject(); + diceCertChain = (byte[]) element.readObject(); + + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + seProvider.onRestore(element); // pop computedHmacKey + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + handleProvisionStatusUpgrade(oldDataTable, oldDataIndex); + } + + private void handleUpgrade(Element element, short oldVersion) { + // Read Primitives + provisionStatus = element.readShort(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_3) { + secureBootMode = element.readByte(); + } + /* check if KeyMint is upgrading from older HAL version to KM300 + * and set the ignore second Imei flag + */ + if (oldVersion < KM_APPLET_PACKAGE_VERSION_4) { + ignoreSecondImei = true; + } else { + ignoreSecondImei = element.readBoolean(); + } + // Read Objects + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_4) { + attIdSecondImei = (byte[]) element.readObject(); + } + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + udsCertChain = (byte[]) element.readObject(); + diceCertChain = (byte[]) element.readObject(); + oemRootPublicKey = (byte[]) element.readObject(); + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + } + + public void getProvisionStatus(byte[] dataTable, byte[] scratchpad, short offset) { + Util.setShort(scratchpad, offset, (short) 0); + readDataEntry(dataTable, OLD_PROVISIONED_STATUS_OFFSET, scratchpad, offset); + } + + void handleProvisionStatusUpgrade(byte[] dataTable, short dataTableIndex) { + short dInex = repository.allocReclaimableMemory((short) 2); + byte data[] = repository.getHeap(); + getProvisionStatus(dataTable, data, dInex); + short pStatus = (short) (data[dInex] & 0x00ff); + if (KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED + == (pStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + pStatus |= + KMKeymasterApplet.PROVISION_STATUS_SE_LOCKED + | KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + } + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 1.0 to 4.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus = pStatus; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + repository.reclaimMemory((short) 2); + // Request object deletion for unreferenced address for additionalCertChain + JCSystem.requestObjectDeletion(); + } + + @Override + public short getBackupPrimitiveByteCount() { + // provisionStatus - 2 bytes + // secureBootMode - 1 byte + // Flag for ignore second Imei- 1 byte + return (short) + (4 + + seProvider.getBackupPrimitiveByteCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } + + @Override + public short getBackupObjectCount() { + // AttestationIds - 9 + // UdsCertificateChain - 1 + // diceCertificateChain - 1 + // oemRootPublicKey - 1 + return (short) + (12 + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupObjectCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java new file mode 100644 index 0000000..2418204 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -0,0 +1,209 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMMap represents an array of a KMType key and a KMType value. Map is the sequence of pairs. Each + * pair is one or more sub-types of KMType. The KMMap instance maps to the CBOR type map. KMMap is a + * KMType and it further extends the value field in TLV_HEADER as MAP_HEADER struct{ short + * subType;short length;} followed by a sequence of pairs. Each pair contains a key and a value as + * short pointers to KMType instances. + */ +public class KMMap extends KMType { + + public static final short ANY_MAP_LENGTH = 0x1000; + private static final byte MAP_HEADER_SIZE = 4; + private static KMMap prototype; + + private KMMap() {} + + private static KMMap proto(short ptr) { + if (prototype == null) { + prototype = new KMMap(); + } + instanceTable[KM_MAP_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(MAP_TYPE, (short) MAP_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_MAP_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(MAP_TYPE, (short) (MAP_HEADER_SIZE + (length * 4))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMMap cast(short ptr) { + if (heap[ptr] != MAP_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short keyPtr, short valPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short keyIndex = + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4)); + Util.setShort(heap, keyIndex, keyPtr); + Util.setShort(heap, (short) (keyIndex + 2), valPtr); + } + + public short getKey(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4))); + } + + public short getKeyValue(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4 + 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + // Swap keys + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4)), + indexPtr1); + + // Swap Values + indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2))); + indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2)), + indexPtr1); + } + + public void canonicalize() { + KMCoseMap.canonicalize(instanceTable[KM_MAP_OFFSET], length()); + } + + public short containedType() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + MAP_HEADER_SIZE); + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public byte[] getBuffer() { + return heap; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java new file mode 100644 index 0000000..3a6404e --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -0,0 +1,134 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8-bit, 16-bit, 32-bit and 64-bit signed integer. It corresponds to CBOR int type. + * struct{byte NEG_INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMNInteger extends KMInteger { + + public static final byte SIGNED_MASK = (byte) 0x80; + private static KMNInteger prototype; + + private KMNInteger() {} + + private static KMNInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMNInteger(); + } + instanceTable[KM_NEG_INTEGER_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + return KMType.exp(NEG_INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = KMInteger.UINT_64; + } else { + length = KMInteger.UINT_32; + } + return KMType.instance(NEG_INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMNInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != NEG_INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_64); + return ptr; + } + + public static boolean isSignedInteger(byte[] num, short offset) { + byte val = num[offset]; + return SIGNED_MASK == (val & SIGNED_MASK); + } + + @Override + protected short getBaseOffset() { + return instanceTable[KM_NEG_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java new file mode 100644 index 0000000..2a53acd --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -0,0 +1,354 @@ +/* + * 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.KMException; +import com.android.javacard.seprovider.KMOperation; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMOperationState is the container of an active operation started by beginOperation function. This + * operation state is persisted by the applet in non volatile memory. However, this state is not + * retained if applet is upgraded. There will be four operation state records maintained i.e. only + * four active operations are supported at any given time. + */ +public class KMOperationState { + + // sizes + public static final byte OPERATION_HANDLE_SIZE = 8; + public static final byte DATA_SIZE = 11; + public static final byte AUTH_TIME_SIZE = 8; + // Secure user ids 5 * 8 = 40 bytes ( Considering Maximum 5 SECURE USER IDs) + // First two bytes are reserved to store number of secure ids. So total 42 bytes. + public static final byte USER_SECURE_IDS_SIZE = 42; + // byte type + private static final byte ALG = 0; + private static final byte PURPOSE = 1; + private static final byte PADDING = 2; + private static final byte BLOCK_MODE = 3; + private static final byte DIGEST = 4; + private static final byte FLAGS = 5; + private static final byte KEY_SIZE = 6; + private static final byte MAC_LENGTH = 7; + private static final byte MGF_DIGEST = 8; + private static final byte AUTH_TYPE = 9; + private static final byte MIN_MAC_LENGTH = 10; + private static final byte OPERATION = 0; + private static final byte HMAC_SIGNER_OPERATION = 1; + // Flag masks + private static final byte AUTH_PER_OP_REQD = 1; + private static final byte SECURE_USER_ID_REQD = 2; + private static final byte AUTH_TIMEOUT_VALIDATED = 4; + private static final byte AES_GCM_UPDATE_ALLOWED = 8; + private static final byte PROCESSED_INPUT_MSG = 16; + // Max user secure ids. + private static final byte MAX_SECURE_USER_IDS = 5; + + // Object References + private byte[] opHandle; + private byte[] authTime; + private byte[] userSecureIds; + private short[] data; + private Object[] operations; + + public KMOperationState() { + opHandle = JCSystem.makeTransientByteArray(OPERATION_HANDLE_SIZE, JCSystem.CLEAR_ON_RESET); + authTime = JCSystem.makeTransientByteArray(AUTH_TIME_SIZE, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientShortArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operations = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + userSecureIds = JCSystem.makeTransientByteArray(USER_SECURE_IDS_SIZE, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public void reset() { + byte index = 0; + while (index < DATA_SIZE) { + data[index] = KMType.INVALID_VALUE; + index++; + } + Util.arrayFillNonAtomic(opHandle, (short) 0, OPERATION_HANDLE_SIZE, (byte) 0); + Util.arrayFillNonAtomic(authTime, (short) 0, AUTH_TIME_SIZE, (byte) 0); + + if (null != operations[OPERATION]) { + ((KMOperation) operations[OPERATION]).abort(); + } + operations[OPERATION] = null; + + if (null != operations[HMAC_SIGNER_OPERATION]) { + ((KMOperation) operations[HMAC_SIGNER_OPERATION]).abort(); + } + operations[HMAC_SIGNER_OPERATION] = null; + } + + public short compare(byte[] handle, short start, short len) { + return Util.arrayCompare(handle, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getKeySize() { + return data[KEY_SIZE]; + } + + public void setKeySize(short keySize) { + data[KEY_SIZE] = keySize; + } + + public short getHandle() { + return KMInteger.uint_64(opHandle, (short) 0); + } + + public void setHandle(byte[] buf, short start, short len) { + Util.arrayCopyNonAtomic(buf, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getPurpose() { + return data[PURPOSE]; + } + + public void setPurpose(short purpose) { + data[PURPOSE] = purpose; + } + + public boolean isInputMsgProcessed() { + return (data[FLAGS] & PROCESSED_INPUT_MSG) != 0; + } + + public KMOperation getOperation() { + return (KMOperation) operations[OPERATION]; + } + + public void setOperation(KMOperation op) { + operations[OPERATION] = op; + } + + public boolean isAuthPerOperationReqd() { + return (data[FLAGS] & AUTH_PER_OP_REQD) != 0; + } + + public void setAuthPerOperationReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | AUTH_PER_OP_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~AUTH_PER_OP_REQD)); + } + } + + public boolean isAuthTimeoutValidated() { + return (data[FLAGS] & AUTH_TIMEOUT_VALIDATED) != 0; + } + + public void setAuthTimeoutValidated(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | AUTH_TIMEOUT_VALIDATED); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~AUTH_TIMEOUT_VALIDATED)); + } + } + + public boolean isSecureUserIdReqd() { + return (data[FLAGS] & SECURE_USER_ID_REQD) != 0; + } + + public short getAuthTime() { + return KMInteger.uint_64(authTime, (short) 0); + } + + public void setAuthTime(byte[] timeBuf, short start) { + Util.arrayCopyNonAtomic(timeBuf, start, authTime, (short) 0, AUTH_TIME_SIZE); + } + + public void setProcessedInputMsg(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | PROCESSED_INPUT_MSG); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~PROCESSED_INPUT_MSG)); + } + } + + public void setOneTimeAuthReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | SECURE_USER_ID_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~SECURE_USER_ID_REQD)); + } + } + + public short getAuthType() { + return data[AUTH_TYPE]; + } + + public void setAuthType(byte authType) { + data[AUTH_TYPE] = authType; + } + + public short getUserSecureId() { + short offset = 0; + short length = Util.getShort(userSecureIds, offset); + offset += 2; + if (length == 0) { + return KMType.INVALID_VALUE; + } + short arrObj = KMArray.instance(length); + short index = 0; + short obj; + while (index < length) { + obj = KMInteger.instance(userSecureIds, (short) (offset + index * 8), (short) 8); + KMArray.cast(arrObj).add(index, obj); + index++; + } + return KMIntegerArrayTag.instance(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, arrObj); + } + + public void setUserSecureId(short integerArrayPtr) { + short length = KMIntegerArrayTag.cast(integerArrayPtr).length(); + if (length > MAX_SECURE_USER_IDS) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + Util.arrayFillNonAtomic(userSecureIds, (short) 0, USER_SECURE_IDS_SIZE, (byte) 0); + short index = 0; + short obj; + short offset = 0; + offset = Util.setShort(userSecureIds, offset, length); + while (index < length) { + obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + Util.arrayCopyNonAtomic( + KMInteger.cast(obj).getBuffer(), + KMInteger.cast(obj).getStartOff(), + userSecureIds, + (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), + KMInteger.cast(obj).length()); + index++; + } + } + + public short getAlgorithm() { + return data[ALG]; + } + + public void setAlgorithm(short algorithm) { + data[ALG] = algorithm; + } + + public short getPadding() { + return data[PADDING]; + } + + public void setPadding(short padding) { + data[PADDING] = padding; + } + + public short getBlockMode() { + return data[BLOCK_MODE]; + } + + public void setBlockMode(short blockMode) { + data[BLOCK_MODE] = blockMode; + } + + public short getDigest() { + return data[DIGEST]; + } + + public void setDigest(byte digest) { + data[DIGEST] = digest; + } + + public short getMgfDigest() { + return data[MGF_DIGEST]; + } + + public void setMgfDigest(byte mgfDigest) { + data[MGF_DIGEST] = mgfDigest; + } + + public boolean isAesGcmUpdateAllowed() { + return (data[FLAGS] & AES_GCM_UPDATE_ALLOWED) != 0; + } + + public void setAesGcmUpdateComplete() { + data[FLAGS] = (byte) (data[FLAGS] & (~AES_GCM_UPDATE_ALLOWED)); + } + + public void setAesGcmUpdateStart() { + data[FLAGS] = (byte) (data[FLAGS] | AES_GCM_UPDATE_ALLOWED); + } + + public short getMinMacLength() { + return data[MIN_MAC_LENGTH]; + } + + public void setMinMacLength(short length) { + data[MIN_MAC_LENGTH] = length; + } + + public short getMacLength() { + return data[MAC_LENGTH]; + } + + public void setMacLength(short length) { + data[MAC_LENGTH] = length; + } + + public byte getBufferingMode() { + short alg = getAlgorithm(); + short purpose = getPurpose(); + short digest = getDigest(); + short padding = getPadding(); + short blockMode = getBlockMode(); + + if (alg == KMType.RSA + && ((digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) + || purpose == KMType.DECRYPT)) { + return KMType.BUF_RSA_DECRYPT_OR_NO_DIGEST; + } + + if (alg == KMType.EC && digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) { + return KMType.BUF_EC_NO_DIGEST; + } + + switch (alg) { + case KMType.AES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && blockMode == KMType.GCM) { + return KMType.BUF_AES_GCM_DECRYPT_BLOCK_ALIGN; + } + break; + case KMType.DES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN; + } + } + return KMType.BUF_NONE; + } + + public KMOperation getTrustedConfirmationSigner() { + return (KMOperation) operations[HMAC_SIGNER_OPERATION]; + } + + public void setTrustedConfirmationSigner(KMOperation hmacSignerOp) { + operations[HMAC_SIGNER_OPERATION] = hmacSignerOp; + } + + public boolean isTrustedConfirmationRequired() { + return operations[HMAC_SIGNER_OPERATION] != null; + } +} 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] + */ + 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; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java new file mode 100644 index 0000000..9fd2406 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -0,0 +1,130 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMRepository class manages volatile memory usage by the applet. Note the repository is only used + * by applet and it is not intended to be used by seProvider. + */ +public class KMRepository { + + // The maximum available heap memory. + public static final short HEAP_SIZE = 10000; + // Index pointing from the back of heap. + private static short[] reclaimIndex; + // Singleton instance + private static KMRepository repository; + // Heap buffer + private byte[] heap; + // Index to the heap buffer. + private short[] heapIndex; + + public KMRepository(boolean isUpgrading) { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + heapIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex[0] = HEAP_SIZE; + repository = this; + } + + public static KMRepository instance() { + return repository; + } + + public void onUninstall() { + // Javacard Runtime environment cleans up the data. + + } + + public void onProcess() {} + + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, HEAP_SIZE, (byte) 0); + heapIndex[0] = 0; + reclaimIndex[0] = HEAP_SIZE; + } + + public void onDeselect() {} + + public void onSelect() { + // If write through caching is implemented then this method will restore the data into cache + } + + // This function uses memory from the back of the heap(transient memory). Call + // reclaimMemory function immediately after the use. + public short allocReclaimableMemory(short length) { + if ((((short) (reclaimIndex[0] - length)) <= heapIndex[0]) || (length >= HEAP_SIZE / 2)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + reclaimIndex[0] -= length; + return reclaimIndex[0]; + } + + // Reclaims the memory back. + public void reclaimMemory(short length) { + if (reclaimIndex[0] < heapIndex[0]) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, reclaimIndex[0], length, (byte) 0); + reclaimIndex[0] += length; + } + + public short allocAvailableMemory() { + if (heapIndex[0] >= heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short index = heapIndex[0]; + heapIndex[0] = reclaimIndex[0]; + return index; + } + + public short alloc(short length) { + if ((((short) (heapIndex[0] + length)) > heap.length) + || (((short) (heapIndex[0] + length)) > reclaimIndex[0])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + heapIndex[0] += length; + return (short) (heapIndex[0] - length); + } + + public byte[] getHeap() { + return heap; + } + + public short getHeapIndex() { + return heapIndex[0]; + } + + // Use this function to reset the heapIndex to its previous state. + // Some of the data might be lost so use it carefully. + public void setHeapIndex(short offset) { + if (offset > heapIndex[0] || offset < 0) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, offset, (short) (heapIndex[0] - offset), (byte) 0); + heapIndex[0] = offset; + } + + public short getHeapReclaimIndex() { + return reclaimIndex[0]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java new file mode 100644 index 0000000..07b2675 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java @@ -0,0 +1,80 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSemanticTag corresponds to CBOR type of tagged item. The structure is defined as struct{byte + * SEMANTIC_TAG_TYPE; short length; tag, short ptr }. Tag is INTEGER_TYPE and the possible values + * are defined here https://www.rfc-editor.org/rfc/rfc7049#section-2.4 + */ +public class KMSemanticTag extends KMType { + + public static final short COSE_MAC_SEMANTIC_TAG = (short) 0x0011; + public static final short ROT_SEMANTIC_TAG = (short) 0x9C41; + private static KMSemanticTag prototype; + + private KMSemanticTag() {} + + private static KMSemanticTag proto(short ptr) { + if (prototype == null) { + prototype = new KMSemanticTag(); + } + instanceTable[KM_SEMANTIC_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp(short valuePtr) { + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMInteger.exp()); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMSemanticTag cast(short ptr) { + if (heap[ptr] != SEMANTIC_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short tag, short value) { + if (!isSemanticTagSupported(tag)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // The maximum tag size can be UINT32. Currently, we support + // only two tags which are short. + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), tag); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), value); + return ptr; + } + + private static boolean isSemanticTagSupported(short tag) { + tag = KMInteger.cast(tag).getShort(); + switch (tag) { + case COSE_MAC_SEMANTIC_TAG: + case ROT_SEMANTIC_TAG: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + 1)); + } + + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java new file mode 100644 index 0000000..6dffd73 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -0,0 +1,71 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSimpleValue corresponds to CBOR type of Simple value. It holds either true, false or NULL + * values. The structure is defined as struct{byte SIMPLE_VALUE_TYPE; short length; simple value } + */ +public class KMSimpleValue extends KMType { + + public static final byte FALSE = (byte) 20; + public static final byte TRUE = (byte) 21; + public static final byte NULL = (byte) 22; + private static KMSimpleValue prototype; + + private KMSimpleValue() {} + + private static KMSimpleValue proto(short ptr) { + if (prototype == null) { + prototype = new KMSimpleValue(); + } + instanceTable[KM_SIMPLE_VALUE_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(SIMPLE_VALUE_TYPE); + } + + public static KMSimpleValue cast(short ptr) { + if (heap[ptr] != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (!isSimpleValueValid(heap[(short) (ptr + 3)])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(byte value) { + if (!isSimpleValueValid(value)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(SIMPLE_VALUE_TYPE, (short) 1); + heap[(short) (ptr + 3)] = value; + return ptr; + } + + private static boolean isSimpleValueValid(byte value) { + switch (value) { + case TRUE: + case FALSE: + case NULL: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); + } + + public byte getValue() { + return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java new file mode 100644 index 0000000..3033a70 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java @@ -0,0 +1,102 @@ +/* + * 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.KMException; +import javacard.framework.Util; + +/** + * This class represents a tag as defined by keymaster hal specifications. It is composed of key + * value pair. The key consists of short tag type e.g. KMType.ENUM and short tag key e.g. + * KMType.ALGORITHM. The key is encoded as uint CBOR type with 4 bytes. This is followed by value + * which can be any CBOR type based on key. struct{byte tag=KMType.TAG_TYPE, short length, value) + * where value is subtype of KMTag i.e. struct{short tagType=one of tag types declared in KMType , + * short tagKey=one of the tag keys declared in KMType, value} where value is one of the sub-types + * of KMType. + */ +public class KMTag extends KMType { + + public static short getTagType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + public static short getKey(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2)); + } + + public static void assertPresence(short params, short tagType, short tagKey, short error) { + if (!isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static void assertAbsence(short params, short tagType, short tagKey, short error) { + if (isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static boolean isPresent(short params, short tagType, short tagKey) { + short tag = KMKeyParameters.findTag(tagType, tagKey, params); + return tag != KMType.INVALID_VALUE; + } + + public static boolean isEqual(short params, short tagType, short tagKey, short value) { + switch (tagType) { + case KMType.ENUM_TAG: + return KMEnumTag.getValue(tagKey, params) == value; + case KMType.UINT_TAG: + case KMType.DATE_TAG: + case KMType.ULONG_TAG: + return KMIntegerTag.isEqual(params, tagType, tagKey, value); + case KMType.ENUM_ARRAY_TAG: + return KMEnumArrayTag.contains(tagKey, value, params); + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + return KMIntegerArrayTag.contains(tagKey, value, params); + } + return false; + } + + public static void assertTrue(boolean condition, short error) { + if (!condition) { + KMException.throwIt(error); + } + } + + public static boolean isValidPublicExponent(short params) { + short pubExp = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, params); + if (pubExp == KMType.INVALID_VALUE) { + return false; + } + pubExp = KMIntegerTag.cast(pubExp).getValue(); + if (!(KMInteger.cast(pubExp).getShort() == 0x01 + && KMInteger.cast(pubExp).getSignificantShort() == 0x01)) { + return false; + } + return true; + } + + public static boolean isValidKeySize(short params) { + short keysize = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, params); + if (keysize == KMType.INVALID_VALUE) { + return false; + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + return KMIntegerTag.cast(keysize).isValidKeySize((byte) alg); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java new file mode 100644 index 0000000..80aebd2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java @@ -0,0 +1,80 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMTextString represents contiguous block of bytes. It corresponds to CBOR type of Text String. It + * extends KMByteBlob by specifying value field as zero or more sequence of bytes. struct{ byte + * TEXT_STR_TYPE; short length; sequence of bytes} + */ +public class KMTextString extends KMByteBlob { + + private static byte OFFSET_SIZE = 2; + + private static KMTextString prototype; + + private KMTextString() {} + + private static KMTextString proto(short ptr) { + if (prototype == null) { + prototype = new KMTextString(); + } + instanceTable[KM_TEXT_STRING_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(TEXT_STRING_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(TEXT_STRING_TYPE, (short) (length + OFFSET_SIZE)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMTextString + public static KMTextString cast(short ptr) { + if (heap[ptr] != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + protected short getBaseOffset() { + return instanceTable[KM_TEXT_STRING_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java new file mode 100644 index 0000000..dc89604 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java @@ -0,0 +1,409 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + // Types + public static final byte BYTE_BLOB_TYPE = 0x01; + public static final byte INTEGER_TYPE = 0x02; + public static final byte ENUM_TYPE = 0x03; + public static final byte TAG_TYPE = 0x04; + public static final byte ARRAY_TYPE = 0x05; + public static final byte KEY_PARAM_TYPE = 0x06; + public static final byte KEY_CHAR_TYPE = 0x07; + public static final byte HW_AUTH_TOKEN_TYPE = 0x08; + public static final byte VERIFICATION_TOKEN_TYPE = 0x09; + public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; + public static final byte X509_CERT = 0x0B; + public static final byte NEG_INTEGER_TYPE = 0x0C; + public static final byte TEXT_STRING_TYPE = 0x0D; + public static final byte MAP_TYPE = 0x0E; + public static final byte COSE_KEY_TYPE = 0x0F; + public static final byte COSE_PAIR_TAG_TYPE = 0x10; + public static final byte COSE_PAIR_INT_TAG_TYPE = 0x20; + public static final byte COSE_PAIR_NEG_INT_TAG_TYPE = 0x30; + public static final byte COSE_PAIR_BYTE_BLOB_TAG_TYPE = 0x40; + public static final byte COSE_PAIR_COSE_KEY_TAG_TYPE = 0x60; + public static final byte COSE_PAIR_SIMPLE_VALUE_TAG_TYPE = 0x70; + public static final byte COSE_PAIR_TEXT_STR_TAG_TYPE = (byte) 0x80; + public static final byte SIMPLE_VALUE_TYPE = (byte) 0x90; + public static final byte COSE_HEADERS_TYPE = (byte) 0xA0; + public static final byte COSE_CERT_PAYLOAD_TYPE = (byte) 0xB0; + public static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + // Tag Types + public static final short INVALID_TAG = 0x0000; + public static final short ENUM_TAG = 0x1000; + public static final short ENUM_ARRAY_TAG = 0x2000; + public static final short UINT_TAG = 0x3000; + public static final short UINT_ARRAY_TAG = 0x4000; + public static final short ULONG_TAG = 0x5000; + public static final short DATE_TAG = 0x6000; + public static final short BOOL_TAG = 0x7000; + public static final short BIGNUM_TAG = (short) 0x8000; + public static final short BYTES_TAG = (short) 0x9000; + public static final short ULONG_ARRAY_TAG = (short) 0xA000; + public static final short TAG_TYPE_MASK = (short) 0xF000; + + // Enum Tag + // Internal tags + public static final short RULE = 0x7FFF; + public static final byte IGNORE_INVALID_TAGS = 0x00; + public static final byte FAIL_ON_INVALID_TAGS = 0x01; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + public static final byte CURVE_25519 = 0x04; + + // KeyBlobUsageRequirements Enum Tag key and values. + public static final short BLOB_USAGE_REQ = 0x012D; + public static final byte STANDALONE = 0x00; + public static final byte REQUIRES_FILE_SYSTEM = 0x01; + + // HardwareAuthenticatorType Enum Tag key and values. + public static final short USER_AUTH_TYPE = 0x01F8; + public static final byte USER_AUTH_NONE = 0x00; + public static final byte PASSWORD = 0x01; + public static final byte FINGERPRINT = 0x02; + public static final byte BOTH = 0x03; + // have to be power of 2 + public static final byte ANY = (byte) 0xFF; + + // Origin Enum Tag key and values. + public static final short ORIGIN = 0x02BE; + public static final byte GENERATED = 0x00; + public static final byte DERIVED = 0x01; + public static final byte IMPORTED = 0x02; + public static final byte UNKNOWN = 0x03; + public static final byte SECURELY_IMPORTED = 0x04; + + // Hardware Type tag key and values + public static final short HARDWARE_TYPE = 0x0130; + public static final byte SOFTWARE = 0x00; + public static final byte TRUSTED_ENVIRONMENT = 0x01; + public static final byte STRONGBOX = 0x02; + + // No Tag + // Derivation Function - No Tag defined + public static final short KEY_DERIVATION_FUNCTION = (short) 0xF001; + public static final byte DERIVATION_NONE = 0x00; + public static final byte RFC5869_SHA256 = 0x01; + public static final byte ISO18033_2_KDF1_SHA1 = 0x02; + public static final byte ISO18033_2_KDF1_SHA256 = 0x03; + public static final byte ISO18033_2_KDF2_SHA1 = 0x04; + public static final byte ISO18033_2_KDF2_SHA256 = 0x05; + + // KeyFormat - No Tag defined. + public static final short KEY_FORMAT = (short) 0xF002; + public static final byte X509 = 0x00; + public static final byte PKCS8 = 0x01; + public static final byte RAW = 0x03; + + // Verified Boot State + public static final short VERIFIED_BOOT_STATE = (short) 0xF003; + public static final byte VERIFIED_BOOT = 0x00; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte UNVERIFIED_BOOT = 0x02; + public static final byte FAILED_BOOT = 0x03; + + // Device Locked + public static final short DEVICE_LOCKED = (short) 0xF006; + public static final byte DEVICE_LOCKED_TRUE = 0x01; + public static final byte DEVICE_LOCKED_FALSE = 0x00; + + // Enum Array Tag + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; + + // OAEP MGF Digests - only SHA-1 is supported in Javacard + public static final short RSA_OAEP_MGF_DIGEST = 0xCB; + + // Integer Tag - UINT, ULONG and DATE + // UINT tags + // Keysize + public static final short KEYSIZE = 0x0003; + // Min Mac Length + public static final short MIN_MAC_LENGTH = 0x0008; + // Min Seconds between OPS + public static final short MIN_SEC_BETWEEN_OPS = 0x0193; + // Max Uses per Boot + public static final short MAX_USES_PER_BOOT = 0x0194; + // UserId + public static final short USERID = 0x01F5; + // Auth Timeout + public static final short AUTH_TIMEOUT = 0x01F9; + // Auth Timeout in Milliseconds + public static final short AUTH_TIMEOUT_MILLIS = 0x7FFF; + // OS Version + public static final short OS_VERSION = 0x02C1; + // OS Patch Level + public static final short OS_PATCH_LEVEL = 0x02C2; + // Vendor Patch Level + public static final short VENDOR_PATCH_LEVEL = 0x02CE; + // Boot Patch Level + public static final short BOOT_PATCH_LEVEL = 0x02CF; + // Mac Length + public static final short MAC_LENGTH = 0x03EB; + // Usage Count Limit + public static final short USAGE_COUNT_LIMIT = 0x195; + + // ULONG tags + // RSA Public Exponent + public static final short RSA_PUBLIC_EXPONENT = 0x00C8; + + // DATE tags + public static final short ACTIVE_DATETIME = 0x0190; + public static final short ORIGINATION_EXPIRE_DATETIME = 0x0191; + public static final short USAGE_EXPIRE_DATETIME = 0x0192; + public static final short CREATION_DATETIME = 0x02BD; + ; + public static final short CERTIFICATE_NOT_BEFORE = 0x03F0; + public static final short CERTIFICATE_NOT_AFTER = 0x03F1; + // Integer Array Tags - ULONG_REP and UINT_REP. + // User Secure Id + public static final short USER_SECURE_ID = (short) 0x01F6; + + // Boolean Tag + // Caller Nonce + public static final short CALLER_NONCE = (short) 0x0007; + // Include Unique Id + public static final short INCLUDE_UNIQUE_ID = (short) 0x00CA; + // Bootloader Only + public static final short BOOTLOADER_ONLY = (short) 0x012E; + // Rollback Resistance + public static final short ROLLBACK_RESISTANCE = (short) 0x012F; + // No Auth Required + public static final short NO_AUTH_REQUIRED = (short) 0x01F7; + // Allow While On Body + public static final short ALLOW_WHILE_ON_BODY = (short) 0x01FA; + // Max Boot Level + public static final short MAX_BOOT_LEVEL = (short) 0x03F2; + // Trusted User Presence Required + public static final short TRUSTED_USER_PRESENCE_REQUIRED = (short) 0x01FB; + // Trusted Confirmation Required + public static final short TRUSTED_CONFIRMATION_REQUIRED = (short) 0x01FC; + // Unlocked Device Required + public static final short UNLOCKED_DEVICE_REQUIRED = (short) 0x01FD; + // Reset Since Id Rotation + public static final short RESET_SINCE_ID_ROTATION = (short) 0x03EC; + // Early boot ended. + public static final short EARLY_BOOT_ONLY = (short) 0x0131; + // Device unique attestation. + public static final short DEVICE_UNIQUE_ATTESTATION = (short) 0x02D0; + + // Byte Tag + // Application Id + public static final short APPLICATION_ID = (short) 0x0259; + // Application Data + public static final short APPLICATION_DATA = (short) 0x02BC; + // Root Of Trust + public static final short ROOT_OF_TRUST = (short) 0x02C0; + // Unique Id + public static final short UNIQUE_ID = (short) 0x02C3; + // Attestation Challenge + public static final short ATTESTATION_CHALLENGE = (short) 0x02C4; + // Attestation Application Id + public static final short ATTESTATION_APPLICATION_ID = (short) 0x02C5; + // Attestation Id Brand + public static final short ATTESTATION_ID_BRAND = (short) 0x02C6; + // Attestation Id Device + public static final short ATTESTATION_ID_DEVICE = (short) 0x02C7; + // Attestation Id Product + public static final short ATTESTATION_ID_PRODUCT = (short) 0x02C8; + // Attestation Id Serial + public static final short ATTESTATION_ID_SERIAL = (short) 0x02C9; + // Attestation Id IMEI + public static final short ATTESTATION_ID_IMEI = (short) 0x02CA; + // Attestation Id SECOND IMEI + public static final short ATTESTATION_ID_SECOND_IMEI = (short) 0x02D3; + // Attestation Id MEID + public static final short ATTESTATION_ID_MEID = (short) 0x02CB; + // Attestation Id Manufacturer + public static final short ATTESTATION_ID_MANUFACTURER = (short) 0x02CC; + // Attestation Id Model + public static final short ATTESTATION_ID_MODEL = (short) 0x02CD; + // Associated Data + public static final short ASSOCIATED_DATA = (short) 0x03E8; + // Nonce + public static final short NONCE = (short) 0x03E9; + // Confirmation Token + public static final short CONFIRMATION_TOKEN = (short) 0x03ED; + // Serial Number - this is a big num but in applet we handle it as byte blob + public static final short CERTIFICATE_SERIAL_NUM = (short) 0x03EE; + // Subject Name + public static final short CERTIFICATE_SUBJECT_NAME = (short) 0x03EF; + + public static final short LENGTH_FROM_PDU = (short) 0xFFFF; + + public static final byte NO_VALUE = (byte) 0xff; + // Support Curves for Eek Chain validation. + public static final byte RKP_CURVE_NONE = 0; + // Type offsets. + public static final byte KM_TYPE_BASE_OFFSET = 0; + public static final byte KM_ARRAY_OFFSET = KM_TYPE_BASE_OFFSET; + public static final byte KM_BOOL_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 1; + public static final byte KM_BYTE_BLOB_OFFSET = KM_TYPE_BASE_OFFSET + 2; + public static final byte KM_BYTE_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 3; + public static final byte KM_ENUM_OFFSET = KM_TYPE_BASE_OFFSET + 4; + public static final byte KM_ENUM_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 5; + public static final byte KM_ENUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 6; + public static final byte KM_HARDWARE_AUTH_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 7; + public static final byte KM_HMAC_SHARING_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 8; + public static final byte KM_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 9; + public static final byte KM_INTEGER_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 10; + public static final byte KM_INTEGER_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 11; + public static final byte KM_KEY_CHARACTERISTICS_OFFSET = KM_TYPE_BASE_OFFSET + 12; + public static final byte KM_KEY_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 13; + public static final byte KM_VERIFICATION_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 14; + public static final byte KM_NEG_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_TEXT_STRING_OFFSET = KM_TYPE_BASE_OFFSET + 16; + public static final byte KM_MAP_OFFSET = KM_TYPE_BASE_OFFSET + 17; + public static final byte KM_COSE_KEY_OFFSET = KM_TYPE_BASE_OFFSET + 18; + public static final byte KM_COSE_KEY_INT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 19; + public static final byte KM_COSE_KEY_NINT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 20; + public static final byte KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 21; + public static final byte KM_COSE_KEY_COSE_KEY_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 22; + public static final byte KM_COSE_KEY_SIMPLE_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 23; + public static final byte KM_SIMPLE_VALUE_OFFSET = KM_TYPE_BASE_OFFSET + 24; + public static final byte KM_COSE_HEADERS_OFFSET = KM_TYPE_BASE_OFFSET + 25; + public static final byte KM_COSE_KEY_TXT_STR_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 26; + public static final byte KM_COSE_CERT_PAYLOAD_OFFSET = KM_TYPE_BASE_OFFSET + 27; + public static final byte KM_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 28; + public static final byte KM_SEMANTIC_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 29; + + // Attestation types + public static final byte NO_CERT = 0; + public static final byte ATTESTATION_CERT = 1; + public static final byte SELF_SIGNED_CERT = 2; + public static final byte FAKE_CERT = 3; + // Buffering Mode + public static final byte BUF_NONE = 0; + public static final byte BUF_RSA_DECRYPT_OR_NO_DIGEST = 1; + public static final byte BUF_EC_NO_DIGEST = 2; + public static final byte BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN = 3; + public static final byte BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN = 4; + public static final byte BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN = 5; + public static final byte BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN = 6; + public static final byte BUF_AES_GCM_DECRYPT_BLOCK_ALIGN = 7; + + // MAX ApplicationID or Application Data size + public static final byte MAX_APP_ID_APP_DATA_SIZE = 64; + // Max attestation challenge size. + public static final short MAX_ATTESTATION_CHALLENGE_SIZE = 128; + // Max certificate serial size. + public static final byte MAX_CERTIFICATE_SERIAL_SIZE = 20; + // Attestation Application ID + public static final short MAX_ATTESTATION_APP_ID_SIZE = 1024; + // Instance table + public static final byte INSTANCE_TABLE_SIZE = 30; + protected static final byte TLV_HEADER_SIZE = 3; + protected static KMRepository repository; + protected static byte[] heap; + protected static short[] instanceTable; + + public static void initialize() { + instanceTable = JCSystem.makeTransientShortArray(INSTANCE_TABLE_SIZE, JCSystem.CLEAR_ON_RESET); + KMType.repository = KMRepository.instance(); + KMType.heap = repository.getHeap(); + } + + public static byte getType(short ptr) { + return heap[ptr]; + } + + public static short length(short ptr) { + return Util.getShort(heap, (short) (ptr + 1)); + } + + public static short getValue(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + protected static short instance(byte type, short length) { + if (length < 0) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = repository.alloc((short) (length + TLV_HEADER_SIZE)); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + protected static short exp(byte type) { + short ptr = repository.alloc(TLV_HEADER_SIZE); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), INVALID_VALUE); + return ptr; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java new file mode 100644 index 0000000..590e73a --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -0,0 +1,129 @@ +/* + * 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 javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMVerificationToken represents VerificationToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte type=VERIFICATION_TOKEN_TYPE; + * short length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following + * elements: {KMInteger Challenge; KMInteger Timestamp; KMByteBlob PARAMETERS_VERIFIED; + * SecurityLevel level; KMByteBlob Mac}. + */ +public class KMVerificationToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte TIMESTAMP = 0x01; + public static final byte MAC = 0x02; + + private static KMVerificationToken prototype; + + private KMVerificationToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMVerificationToken proto(short ptr) { + if (prototype == null) { + prototype = new KMVerificationToken(); + } + KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMVerificationToken cast(short ptr) { + if (heap[ptr] != VERIFICATION_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} -- cgit v1.2.3 From 23bbb05a221ad52d25e2e9c1a71a4b8a264aa6ed Mon Sep 17 00:00:00 2001 From: avinashhedage Date: Wed, 11 Jan 2023 08:46:10 +0000 Subject: Added android reay_se HAL 300 implementation Test: run vts -m VtsAidlKeyMintTarget Change-Id: Ie88502db96465cf6bccd07dd151d00a5a34eb7ca --- ready_se/google/keymint/KM300/HAL/.clang-format | 10 + ready_se/google/keymint/KM300/HAL/Android.bp | 117 +++++ .../google/keymint/KM300/HAL/CborConverter.cpp | 521 +++++++++++++++++++++ ready_se/google/keymint/KM300/HAL/CborConverter.h | 142 ++++++ .../keymint/KM300/HAL/JavacardKeyMintDevice.cpp | 454 ++++++++++++++++++ .../keymint/KM300/HAL/JavacardKeyMintDevice.h | 124 +++++ .../keymint/KM300/HAL/JavacardKeyMintOperation.cpp | 300 ++++++++++++ .../keymint/KM300/HAL/JavacardKeyMintOperation.h | 136 ++++++ .../JavacardRemotelyProvisionedComponentDevice.cpp | 320 +++++++++++++ .../JavacardRemotelyProvisionedComponentDevice.h | 80 ++++ .../keymint/KM300/HAL/JavacardSecureElement.cpp | 157 +++++++ .../keymint/KM300/HAL/JavacardSecureElement.h | 116 +++++ .../keymint/KM300/HAL/JavacardSharedSecret.cpp | 61 +++ .../keymint/KM300/HAL/JavacardSharedSecret.h | 34 ++ ready_se/google/keymint/KM300/HAL/LICENSE | 202 ++++++++ ready_se/google/keymint/KM300/HAL/METADATA | 3 + ready_se/google/keymint/KM300/HAL/OWNERS | 3 + ...are.hardware_keystore.jc-strongbox-keymint3.xml | 17 + ...hardware.security.keymint3-service.strongbox.rc | 3 + ...ardware.security.keymint3-service.strongbox.xml | 12 + ...re.security.sharedsecret3-service.strongbox.xml | 6 + .../google/keymint/KM300/HAL/keymint_utils.cpp | 130 +++++ ready_se/google/keymint/KM300/HAL/keymint_utils.h | 47 ++ ready_se/google/keymint/KM300/HAL/service.cpp | 95 ++++ 24 files changed, 3090 insertions(+) create mode 100644 ready_se/google/keymint/KM300/HAL/.clang-format create mode 100644 ready_se/google/keymint/KM300/HAL/Android.bp create mode 100644 ready_se/google/keymint/KM300/HAL/CborConverter.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/CborConverter.h create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h create mode 100644 ready_se/google/keymint/KM300/HAL/LICENSE create mode 100644 ready_se/google/keymint/KM300/HAL/METADATA create mode 100644 ready_se/google/keymint/KM300/HAL/OWNERS create mode 100644 ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml create mode 100644 ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc create mode 100644 ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml create mode 100644 ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml create mode 100644 ready_se/google/keymint/KM300/HAL/keymint_utils.cpp create mode 100644 ready_se/google/keymint/KM300/HAL/keymint_utils.h create mode 100644 ready_se/google/keymint/KM300/HAL/service.cpp diff --git a/ready_se/google/keymint/KM300/HAL/.clang-format b/ready_se/google/keymint/KM300/HAL/.clang-format new file mode 100644 index 0000000..b0dc94c --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +IndentCaseLabels: false +ColumnLimit: 100 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 diff --git a/ready_se/google/keymint/KM300/HAL/Android.bp b/ready_se/google/keymint/KM300/HAL/Android.bp new file mode 100644 index 0000000..fd38d10 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/Android.bp @@ -0,0 +1,117 @@ +// 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 { + default_applicable_licenses: [ + "external_libese_ready_se_google_keymint_KM300_HAL_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_libese_ready_se_google_keymint_KM300_HAL_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + +cc_library { + name: "libjc_keymint3", + defaults: [ + "keymaster_defaults", + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "CborConverter.cpp", + "JavacardKeyMintDevice.cpp", + "JavacardKeyMintOperation.cpp", + "JavacardRemotelyProvisionedComponentDevice.cpp", + "JavacardSecureElement.cpp", + "JavacardSharedSecret.cpp", + "keymint_utils.cpp", + ], + cflags: ["-O0"], + shared_libs: [ + "android.hardware.security.secureclock-V1-ndk", + "android.hardware.security.sharedsecret-V1-ndk", + "android.hardware.security.rkp-V3-ndk", + "lib_android_keymaster_keymint_utils", + "libbase", + "libcppbor_external", + "libkeymaster_portable", + "libkeymaster_messages", + "libsoft_attestation_cert", + "liblog", + "libcrypto", + "libcutils", + "libjc_keymint_transport", + "libbinder_ndk", + ], + export_include_dirs: [ + ".", + ], + vendor_available: true, +} + +cc_binary { + name: "android.hardware.security.keymint3-service.strongbox", + relative_install_path: "hw", + init_rc: ["android.hardware.security.keymint3-service.strongbox.rc"], + vintf_fragments: [ + "android.hardware.security.keymint3-service.strongbox.xml", + "android.hardware.security.sharedsecret3-service.strongbox.xml", + ], + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + shared_libs: [ + "android.hardware.security.sharedsecret-V1-ndk", + "lib_android_keymaster_keymint_utils", + "android.hardware.security.rkp-V3-ndk", + "libbase", + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libjc_keymint3", + "libjc_keymint_transport", + "liblog", + "libutils", + "android.se.omapi-V1-ndk", + ], + srcs: [ + "service.cpp", + ], + required: [ + "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", + ], +} + +prebuilt_etc { + name: "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", + sub_dir: "permissions", + vendor: true, + src: "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", +} diff --git a/ready_se/google/keymint/KM300/HAL/CborConverter.cpp b/ready_se/google/keymint/KM300/HAL/CborConverter.cpp new file mode 100644 index 0000000..7d0fc23 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/CborConverter.cpp @@ -0,0 +1,521 @@ +/* + ** + ** Copyright 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. + */ + +#include "CborConverter.h" + +#include +#include + +#include + +#include + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::KeyParameterValue; +using ::aidl::android::hardware::security::keymint::SecurityLevel; +using ::aidl::android::hardware::security::keymint::km_utils::kmParam2Aidl; +using ::aidl::android::hardware::security::keymint::km_utils::legacy_enum_conversion; +using ::aidl::android::hardware::security::keymint::km_utils::typeFromTag; + +constexpr int SB_ENFORCED = 0; +constexpr int TEE_ENFORCED = 1; +constexpr int SW_ENFORCED = 2; + +namespace { + +template +std::optional aidlEnumVal2Uint32(const KeyParameterValue& value) { + return (value.getTag() == aidl_tag) + ? std::optional(static_cast(value.get())) + : std::nullopt; +} + +std::optional aidlEnumParam2Uint32(const KeyParameter& param) { + auto tag = legacy_enum_conversion(param.tag); + switch (tag) { + case KM_TAG_PURPOSE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_ALGORITHM: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_BLOCK_MODE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_DIGEST: + case KM_TAG_RSA_OAEP_MGF_DIGEST: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_PADDING: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_EC_CURVE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_USER_AUTH_TYPE: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_ORIGIN: + return aidlEnumVal2Uint32(param.value); + case KM_TAG_BLOB_USAGE_REQUIREMENTS: + case KM_TAG_KDF: + default: + CHECK(false) << "Unknown or unused enum tag: Something is broken"; + return std::nullopt; + } +} + +} // namespace + +bool CborConverter::addAttestationKey(Array& array, + const std::optional& attestationKey) { + if (attestationKey.has_value()) { + array.add(Bstr(attestationKey->keyBlob)); + addKeyparameters(array, attestationKey->attestKeyParams); + array.add(Bstr(attestationKey->issuerSubjectName)); + } else { + array.add(std::move(Bstr(vector(0)))); + array.add(std::move(Map())); + array.add(std::move(Bstr(vector(0)))); + } + return true; +} + +bool CborConverter::addKeyparameters(Array& array, const vector& keyParams) { + Map map; + std::map> enum_repetition; + std::map uint_repetition; + for (auto& param : keyParams) { + auto tag = legacy_enum_conversion(param.tag); + switch (typeFromTag(tag)) { + case KM_ENUM: { + auto paramEnum = aidlEnumParam2Uint32(param); + if (paramEnum.has_value()) { + map.add(static_cast(tag), *paramEnum); + } + break; + } + case KM_UINT: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get(); + map.add(static_cast(tag), intVal); + } + break; + case KM_UINT_REP: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get(); + uint_repetition[static_cast(tag)].add(intVal); + } + break; + case KM_ENUM_REP: { + auto paramEnumRep = aidlEnumParam2Uint32(param); + if (paramEnumRep.has_value()) { + enum_repetition[static_cast(tag)].push_back(*paramEnumRep); + } + break; + } + case KM_ULONG: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get(); + map.add(static_cast(tag), longVal); + } + break; + case KM_ULONG_REP: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get(); + uint_repetition[static_cast(tag & 0x00000000ffffffff)].add(longVal); + } + break; + case KM_DATE: + if (param.value.getTag() == KeyParameterValue::dateTime) { + auto dateVal = param.value.get(); + map.add(static_cast(tag), dateVal); + } + break; + case KM_BOOL: + map.add(static_cast(tag), 1 /* true */); + break; + case KM_BIGNUM: + case KM_BYTES: + if (param.value.getTag() == KeyParameterValue::blob) { + const auto& value = param.value.get(); + map.add(static_cast(tag & 0x00000000ffffffff), value); + } + break; + case KM_INVALID: + break; + } + } + + for (auto const& [key, val] : enum_repetition) { + Bstr bstr(val); + map.add(key, std::move(bstr)); + } + + for (auto& [key, val] : uint_repetition) { + map.add(key, std::move(val)); + } + array.add(std::move(map)); + return true; +} + +// Array of three maps +std::optional> +CborConverter::getKeyCharacteristics(const unique_ptr& item, const uint32_t pos) { + vector keyCharacteristics; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + KeyCharacteristics swEnf{SecurityLevel::KEYSTORE, {}}; + KeyCharacteristics teeEnf{SecurityLevel::TRUSTED_ENVIRONMENT, {}}; + KeyCharacteristics sbEnf{SecurityLevel::STRONGBOX, {}}; + + auto optSbEnf = getKeyParameters(arrayItem.value(), SB_ENFORCED); + if (!optSbEnf) { + return std::nullopt; + } + sbEnf.authorizations = std::move(optSbEnf.value()); + auto optTeeEnf = getKeyParameters(arrayItem.value(), TEE_ENFORCED); + if (!optTeeEnf) { + return std::nullopt; + } + teeEnf.authorizations = std::move(optTeeEnf.value()); + auto optSwEnf = getKeyParameters(arrayItem.value(), SW_ENFORCED); + if (!optSwEnf) { + return std::nullopt; + } + swEnf.authorizations = std::move(optSwEnf.value()); + // VTS will fail if the authorizations list is empty. + if (!sbEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(sbEnf)); + if (!teeEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(teeEnf)); + if (!swEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(swEnf)); + return keyCharacteristics; +} + +std::optional> CborConverter::getKeyParameter( + const std::pair&, const std::unique_ptr&> pair) { + std::vector keyParams; + keymaster_tag_t key; + auto optValue = getUint64(pair.first); + if (!optValue) { + return std::nullopt; + } + key = static_cast(optValue.value()); + switch (keymaster_tag_get_type(key)) { + case KM_ENUM_REP: { + /* ENUM_REP contains values encoded in a Byte string */ + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) { + return std::nullopt; + } + for (auto bchar : bstr->value()) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + keyParam.enumerated = bchar; + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ENUM: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.enumerated = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.integer = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_ULONG: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT_REP: { + /* UINT_REP contains values encoded in a Array */ + Array* array = const_cast(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.integer = static_cast(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ULONG_REP: { + /* ULONG_REP contains values encoded in a Array */ + Array* array = const_cast(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_DATE: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.date_time = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BOOL: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + // If a tag with this type is present, the value is true. If absent, false. + keyParam.boolean = true; + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BIGNUM: + case KM_BYTES: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) return std::nullopt; + keyParam.blob.data = bstr->value().data(); + keyParam.blob.data_length = bstr->value().size(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_INVALID: + break; + } + return std::nullopt; +} + +// array of a blobs +std::optional> +CborConverter::getCertificateChain(const std::unique_ptr& item, const uint32_t pos) { + vector certChain; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) return std::nullopt; + + const Array* arr = arrayItem.value().get()->asArray(); + for (int i = 0; i < arr->size(); i++) { + Certificate cert; + auto optTemp = getByteArrayVec(arrayItem.value(), i); + if (!optTemp) return std::nullopt; + cert.encodedCertificate = std::move(optTemp.value()); + certChain.push_back(std::move(cert)); + } + return certChain; +} + +std::optional CborConverter::getTextStr(const unique_ptr& item, const uint32_t pos) { + auto textStrItem = getItemAtPos(item, pos); + if (!textStrItem || (MajorType::TSTR != getType(textStrItem.value()))) { + return std::nullopt; + } + const Tstr* tstr = textStrItem.value().get()->asTstr(); + return tstr->value(); +} + +std::optional CborConverter::getByteArrayStr(const unique_ptr& item, + const uint32_t pos) { + auto optTemp = getByteArrayVec(item, pos); + if (!optTemp) { + return std::nullopt; + } + std::string str(optTemp->begin(), optTemp->end()); + return str; +} + +std::optional> CborConverter::getByteArrayVec(const unique_ptr& item, + const uint32_t pos) { + auto strItem = getItemAtPos(item, pos); + if (!strItem || (MajorType::BSTR != getType(strItem.value()))) { + return std::nullopt; + } + const Bstr* bstr = strItem.value().get()->asBstr(); + return bstr->value(); +} + +std::optional +CborConverter::getSharedSecretParameters(const unique_ptr& item, const uint32_t pos) { + SharedSecretParameters params; + // Array [seed, nonce] + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + auto optSeed = getByteArrayVec(arrayItem.value(), 0); + auto optNonce = getByteArrayVec(arrayItem.value(), 1); + if (!optSeed || !optNonce) { + return std::nullopt; + } + params.seed = std::move(optSeed.value()); + params.nonce = std::move(optNonce.value()); + return params; +} + +bool CborConverter::addSharedSecretParameters(Array& array, + const vector& params) { + Array cborParamsVec; + for (auto param : params) { + Array cborParam; + cborParam.add(Bstr(param.seed)); + cborParam.add(Bstr(param.nonce)); + cborParamsVec.add(std::move(cborParam)); + } + array.add(std::move(cborParamsVec)); + return true; +} + +bool CborConverter::addTimeStampToken(Array& array, const TimeStampToken& token) { + Array vToken; + vToken.add(static_cast(token.challenge)); + vToken.add(static_cast(token.timestamp.milliSeconds)); + vToken.add((std::vector(token.mac))); + array.add(std::move(vToken)); + return true; +} + +bool CborConverter::addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken) { + + Array hwAuthToken; + hwAuthToken.add(static_cast(authToken.challenge)); + hwAuthToken.add(static_cast(authToken.userId)); + hwAuthToken.add(static_cast(authToken.authenticatorId)); + hwAuthToken.add(static_cast(authToken.authenticatorType)); + hwAuthToken.add(static_cast(authToken.timestamp.milliSeconds)); + hwAuthToken.add((std::vector(authToken.mac))); + array.add(std::move(hwAuthToken)); + return true; +} + +std::optional CborConverter::getTimeStampToken(const unique_ptr& item, + const uint32_t pos) { + TimeStampToken token; + // {challenge, timestamp, Mac} + auto optChallenge = getUint64(item, pos); + auto optTimestampMillis = getUint64(item, pos + 1); + auto optTemp = getByteArrayVec(item, pos + 2); + if (!optChallenge || !optTimestampMillis || !optTemp) { + return std::nullopt; + } + token.mac = std::move(optTemp.value()); + token.challenge = static_cast(std::move(optChallenge.value())); + token.timestamp.milliSeconds = static_cast(std::move(optTimestampMillis.value())); + return token; +} + +std::optional CborConverter::getArrayItem(const std::unique_ptr& item, + const uint32_t pos) { + Array array; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + array = std::move(*(arrayItem.value().get()->asArray())); + return array; +} + +std::optional CborConverter::getMapItem(const std::unique_ptr& item, + const uint32_t pos) { + Map map; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) { + return std::nullopt; + } + map = std::move(*(mapItem.value().get()->asMap())); + return map; +} + +std::optional> CborConverter::getKeyParameters(const unique_ptr& item, + const uint32_t pos) { + vector params; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) return std::nullopt; + const Map* map = mapItem.value().get()->asMap(); + size_t mapSize = map->size(); + for (int i = 0; i < mapSize; i++) { + auto optKeyParams = getKeyParameter((*map)[i]); + if (optKeyParams) { + params.insert(params.end(), optKeyParams->begin(), optKeyParams->end()); + } else { + return std::nullopt; + } + } + return params; +} + +std::tuple, keymaster_error_t> +CborConverter::decodeData(const std::vector& response) { + auto [item, pos, message] = cppbor::parse(response); + if (!item || MajorType::ARRAY != getType(item)) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + auto optErrorCode = getErrorCode(item, 0); + if (!optErrorCode) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + return {std::move(item), optErrorCode.value()}; +} + +std::optional +CborConverter::getErrorCode(const std::unique_ptr& item, const uint32_t pos) { + auto optErrorVal = getUint64(item, pos); + if (!optErrorVal) { + return std::nullopt; + } + return static_cast(0 - optErrorVal.value()); +} + +std::optional CborConverter::getUint64(const unique_ptr& item) { + if ((item == nullptr) || (MajorType::UINT != getType(item))) { + return std::nullopt; + } + const Uint* uintVal = item.get()->asUint(); + return uintVal->unsignedValue(); +} + +std::optional CborConverter::getUint64(const unique_ptr& item, const uint32_t pos) { + auto intItem = getItemAtPos(item, pos); + if (!intItem) { + return std::nullopt; + } + return getUint64(intItem.value()); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/CborConverter.h b/ready_se/google/keymint/KM300/HAL/CborConverter.h new file mode 100644 index 0000000..d594350 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/CborConverter.h @@ -0,0 +1,142 @@ +/* + ** + ** Copyright 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. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +namespace keymint::javacard { +using aidl::android::hardware::security::keymint::AttestationKey; +using aidl::android::hardware::security::keymint::Certificate; +using aidl::android::hardware::security::keymint::HardwareAuthToken; +using aidl::android::hardware::security::keymint::KeyCharacteristics; +using aidl::android::hardware::security::keymint::KeyParameter; +using aidl::android::hardware::security::secureclock::TimeStampToken; +using aidl::android::hardware::security::sharedsecret::SharedSecretParameters; +using cppbor::Array; +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Item; +using cppbor::MajorType; +using cppbor::Map; +using cppbor::Nint; +using cppbor::Tstr; +using cppbor::Uint; +using std::string; +using std::unique_ptr; +using std::vector; + +class CborConverter { + public: + CborConverter() = default; + + ~CborConverter() = default; + + std::tuple, keymaster_error_t> + decodeData(const std::vector& response); + + std::optional getUint64(const unique_ptr& item); + + std::optional getUint64(const unique_ptr& item, const uint32_t pos); + + std::optional + getSharedSecretParameters(const std::unique_ptr& item, const uint32_t pos); + + std::optional getByteArrayStr(const unique_ptr& item, const uint32_t pos); + + std::optional getTextStr(const unique_ptr& item, const uint32_t pos); + + std::optional> getByteArrayVec(const unique_ptr& item, + const uint32_t pos); + + std::optional> getKeyParameters(const unique_ptr& item, + const uint32_t pos); + + bool addKeyparameters(Array& array, const vector& keyParams); + + bool addAttestationKey(Array& array, const std::optional& attestationKey); + + bool addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken); + + bool addSharedSecretParameters(Array& array, const vector& params); + + std::optional getTimeStampToken(const std::unique_ptr& item, + const uint32_t pos); + + std::optional> + getKeyCharacteristics(const std::unique_ptr& item, const uint32_t pos); + + std::optional> getCertificateChain(const std::unique_ptr& item, + const uint32_t pos); + + std::optional>> getMultiByteArray(const unique_ptr& item, + const uint32_t pos); + + bool addTimeStampToken(Array& array, const TimeStampToken& token); + + std::optional getMapItem(const std::unique_ptr& item, const uint32_t pos); + + std::optional getArrayItem(const std::unique_ptr& item, const uint32_t pos); + + std::optional getErrorCode(const std::unique_ptr& item, + const uint32_t pos); + + private: + /** + * Get the type of the Item pointer. + */ + inline MajorType getType(const unique_ptr& item) { return item.get()->type(); } + + /** + * Construct Keyparameter structure from the pair of key and value. If TagType is ENUM_REP the + * value contains binary string. If TagType is UINT_REP or ULONG_REP the value contains Array of + * unsigned integers. + */ + std::optional> getKeyParameter( + const std::pair&, const std::unique_ptr&> pair); + + /** + * Get the sub item pointer from the root item pointer at the given position. + */ + inline std::optional> getItemAtPos(const unique_ptr& item, + const uint32_t pos) { + Array* arr = nullptr; + + if (MajorType::ARRAY != getType(item)) { + return std::nullopt; + } + arr = const_cast(item.get()->asArray()); + if (arr->size() < (pos + 1)) { + return std::nullopt; + } + return std::move((*arr)[pos]); + } +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp new file mode 100644 index 0000000..bd68b48 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp @@ -0,0 +1,454 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" + +#include "JavacardKeyMintDevice.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "JavacardKeyMintOperation.h" +#include "JavacardSharedSecret.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Uint; +using ::keymaster::AuthorizationSet; +using ::keymaster::dup_buffer; +using ::keymaster::KeymasterBlob; +using ::keymaster::KeymasterKeyBlob; +using ::keymint::javacard::Instruction; +using std::string; + +ScopedAStatus JavacardKeyMintDevice::defaultHwInfo(KeyMintHardwareInfo* info) { + info->versionNumber = 2; + info->keyMintAuthorName = "Google"; + info->keyMintName = "JavacardKeymintDevice"; + info->securityLevel = securitylevel_; + info->timestampTokenRequired = true; + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_HW_INFO_CMD); + std::optional optKeyMintName; + std::optional optKeyMintAuthorName; + std::optional optSecLevel; + std::optional optVersion; + std::optional optTsRequired; + if (err != KM_ERROR_OK || !(optVersion = cbor_.getUint64(item, 1)) || + !(optSecLevel = cbor_.getUint64(item, 2)) || + !(optKeyMintName = cbor_.getByteArrayStr(item, 3)) || + !(optKeyMintAuthorName = cbor_.getByteArrayStr(item, 4)) || + !(optTsRequired = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + card_->initializeJavacard(); + info->keyMintName = std::move(optKeyMintName.value()); + info->keyMintAuthorName = std::move(optKeyMintAuthorName.value()); + info->timestampTokenRequired = (optTsRequired.value() == 1); + info->securityLevel = static_cast(std::move(optSecLevel.value())); + info->versionNumber = static_cast(std::move(optVersion.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::generateKey(const vector& keyParams, + const optional& attestationKey, + KeyCreationResult* creationResult) { + cppbor::Array array; + // add key params + cbor_.addKeyparameters(array, keyParams); + // add attestation key if any + cbor_.addAttestationKey(array, attestationKey); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding og response in generateKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::addRngEntropy(const vector& data) { + cppbor::Array request; + // add key data + request.add(Bstr(data)); + auto [item, err] = card_->sendRequest(Instruction::INS_ADD_RNG_ENTROPY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending addRngEntropy."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::importKey(const vector& keyParams, + KeyFormat keyFormat, const vector& keyData, + const optional& attestationKey, + KeyCreationResult* creationResult) { + + cppbor::Array request; + // add key params + cbor_.addKeyparameters(request, keyParams); + // add key format + request.add(Uint(static_cast(keyFormat))); + // add key data + request.add(Bstr(keyData)); + // add attestation key if any + cbor_.addAttestationKey(request, attestationKey); + + auto [item, err] = card_->sendRequest(Instruction::INS_IMPORT_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in importKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding response in importKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +// import wrapped key is divided into 2 stage operation. +ScopedAStatus JavacardKeyMintDevice::importWrappedKey(const vector& wrappedKeyData, + const vector& wrappingKeyBlob, + const vector& maskingKey, + const vector& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) { + cppbor::Array request; + std::unique_ptr item; + vector keyBlob; + std::vector response; + vector keyCharacteristics; + std::vector iv; + std::vector transitKey; + std::vector secureKey; + std::vector tag; + vector authList; + KeyFormat keyFormat; + std::vector wrappedKeyDescription; + keymaster_error_t errorCode = parseWrappedKey(wrappedKeyData, iv, transitKey, secureKey, tag, + authList, keyFormat, wrappedKeyDescription); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in parse wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + + // begin import + std::tie(item, errorCode) = + sendBeginImportWrappedKeyCmd(transitKey, wrappingKeyBlob, maskingKey, unwrappingParams); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send begin import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + // Finish the import + std::tie(item, errorCode) = sendFinishImportWrappedKeyCmd( + authList, keyFormat, secureKey, tag, iv, wrappedKeyDescription, passwordSid, biometricSid); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send finish import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding the response in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +std::tuple, keymaster_error_t> +JavacardKeyMintDevice::sendBeginImportWrappedKeyCmd(const std::vector& transitKey, + const std::vector& wrappingKeyBlob, + const std::vector& maskingKey, + const vector& unwrappingParams) { + Array request; + request.add(std::vector(transitKey)); + request.add(std::vector(wrappingKeyBlob)); + request.add(std::vector(maskingKey)); + cbor_.addKeyparameters(request, unwrappingParams); + return card_->sendRequest(Instruction::INS_BEGIN_IMPORT_WRAPPED_KEY_CMD, request); +} + +std::tuple, keymaster_error_t> +JavacardKeyMintDevice::sendFinishImportWrappedKeyCmd( + const vector& keyParams, KeyFormat keyFormat, + const std::vector& secureKey, const std::vector& tag, + const std::vector& iv, const std::vector& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid) { + Array request; + cbor_.addKeyparameters(request, keyParams); + request.add(static_cast(keyFormat)); + request.add(std::vector(secureKey)); + request.add(std::vector(tag)); + request.add(std::vector(iv)); + request.add(std::vector(wrappedKeyDescription)); + request.add(Uint(passwordSid)); + request.add(Uint(biometricSid)); + return card_->sendRequest(Instruction::INS_FINISH_IMPORT_WRAPPED_KEY_CMD, request); +} + +ScopedAStatus JavacardKeyMintDevice::upgradeKey(const vector& keyBlobToUpgrade, + const vector& upgradeParams, + vector* keyBlob) { + cppbor::Array request; + // add key blob + request.add(Bstr(keyBlobToUpgrade)); + // add key params + cbor_.addKeyparameters(request, upgradeParams); + auto [item, err] = card_->sendRequest(Instruction::INS_UPGRADE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + if (!optKeyBlob) { + LOG(ERROR) << "Error in decoding the response in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteKey(const vector& keyBlob) { + Array request; + request.add(Bstr(keyBlob)); + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteKey."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteAllKeys() { + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_ALL_KEYS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteAllKeys."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::destroyAttestationIds() { + auto [item, err] = card_->sendRequest(Instruction::INS_DESTROY_ATT_IDS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in destroyAttestationIds."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::begin(KeyPurpose purpose, const std::vector& keyBlob, + const std::vector& params, + const std::optional& authToken, + BeginResult* result) { + + cppbor::Array array; + std::vector response; + // make request + array.add(Uint(static_cast(purpose))); + array.add(Bstr(keyBlob)); + cbor_.addKeyparameters(array, params); + HardwareAuthToken token = authToken.value_or(HardwareAuthToken()); + cbor_.addHardwareAuthToken(array, token); + + // Send earlyBootEnded if there is any pending earlybootEnded event. + auto retErr = card_->sendEarlyBootEndedEvent(false); + if (retErr != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(retErr); + ; + } + + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_OPERATION_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in begin."; + return km_utils::kmError2ScopedAStatus(err); + } + // return the result + auto keyParams = cbor_.getKeyParameters(item, 1); + auto optOpHandle = cbor_.getUint64(item, 2); + auto optBufMode = cbor_.getUint64(item, 3); + auto optMacLength = cbor_.getUint64(item, 4); + + if (!keyParams || !optOpHandle || !optBufMode || !optMacLength) { + LOG(ERROR) << "Error in decoding the response in begin."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + result->params = std::move(keyParams.value()); + result->challenge = optOpHandle.value(); + result->operation = ndk::SharedRefBase::make( + static_cast(optOpHandle.value()), + static_cast(optBufMode.value()), optMacLength.value(), card_); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardKeyMintDevice::deviceLocked(bool passwordOnly, + const std::optional& timestampToken) { + Array request; + int8_t password = 1; + if (!passwordOnly) { + password = 0; + } + request.add(Uint(password)); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_DEVICE_LOCKED_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::earlyBootEnded() { + auto err = card_->sendEarlyBootEndedEvent(true); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBootEndedEvent."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getKeyCharacteristics( + const std::vector& keyBlob, const std::vector& appId, + const std::vector& appData, std::vector* result) { + cppbor::Array request; + request.add(vector(keyBlob)); + request.add(vector(appId)); + request.add(vector(appData)); + auto [item, err] = card_->sendRequest(Instruction::INS_GET_KEY_CHARACTERISTICS_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getKeyCharacteristics."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyChars = cbor_.getKeyCharacteristics(item, 1); + if (!optKeyChars) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *result = std::move(optKeyChars.value()); + return ScopedAStatus::ok(); +} + +keymaster_error_t +JavacardKeyMintDevice::parseWrappedKey(const vector& wrappedKeyData, + std::vector& iv, std::vector& transitKey, + std::vector& secureKey, std::vector& tag, + vector& authList, KeyFormat& keyFormat, + std::vector& wrappedKeyDescription) { + KeymasterBlob kmIv; + KeymasterKeyBlob kmTransitKey; + KeymasterKeyBlob kmSecureKey; + KeymasterBlob kmTag; + AuthorizationSet authSet; + keymaster_key_format_t kmKeyFormat; + KeymasterBlob kmWrappedKeyDescription; + + size_t keyDataLen = wrappedKeyData.size(); + uint8_t* keyData = dup_buffer(wrappedKeyData.data(), keyDataLen); + keymaster_key_blob_t keyMaterial = {keyData, keyDataLen}; + keymaster_error_t error = + parse_wrapped_key(KeymasterKeyBlob(keyMaterial), &kmIv, &kmTransitKey, &kmSecureKey, &kmTag, + &authSet, &kmKeyFormat, &kmWrappedKeyDescription); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error parsing wrapped key."; + return error; + } + iv = km_utils::kmBlob2vector(kmIv); + transitKey = km_utils::kmBlob2vector(kmTransitKey); + secureKey = km_utils::kmBlob2vector(kmSecureKey); + tag = km_utils::kmBlob2vector(kmTag); + authList = km_utils::kmParamSet2Aidl(authSet); + keyFormat = static_cast(kmKeyFormat); + wrappedKeyDescription = km_utils::kmBlob2vector(kmWrappedKeyDescription); + return KM_ERROR_OK; +} + +ScopedAStatus JavacardKeyMintDevice::convertStorageKeyToEphemeral( + const std::vector& /* storageKeyBlob */, + std::vector* /* ephemeralKeyBlob */) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrustChallenge(array* challenge) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_ROT_CHALLENGE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getRootOfTrustChallenge."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optChallenge = cbor_.getByteArrayVec(item, 1); + if (!optChallenge) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + std::move(optChallenge->begin(), optChallenge->begin() + 16, challenge->begin()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrust(const array& /*challenge*/, + vector* /*rootOfTrust*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::sendRootOfTrust(const vector& rootOfTrust) { + cppbor::Array request; + request.add(EncodedItem(rootOfTrust)); // taggedItem. + auto [item, err] = card_->sendRequest(Instruction::INS_SEND_ROT_DATA_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in sendRootOfTrust."; + return km_utils::kmError2ScopedAStatus(err); + } + LOG(INFO) << "JavacardKeyMintDevice::sendRootOfTrust success"; + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h new file mode 100644 index 0000000..adf0f7d --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h @@ -0,0 +1,124 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include +#include +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::array; +using std::optional; +using std::shared_ptr; +using std::vector; + +class JavacardKeyMintDevice : public BnKeyMintDevice { + public: + explicit JavacardKeyMintDevice(shared_ptr card) + : securitylevel_(SecurityLevel::STRONGBOX), card_(card) { + card_->initializeJavacard(); + } + virtual ~JavacardKeyMintDevice() {} + + ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* info) override; + + ScopedAStatus addRngEntropy(const vector& data) override; + + ScopedAStatus generateKey(const vector& keyParams, + const optional& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importKey(const vector& keyParams, KeyFormat keyFormat, + const vector& keyData, + const optional& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importWrappedKey(const vector& wrappedKeyData, + const vector& wrappingKeyBlob, + const vector& maskingKey, + const vector& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) override; + + ScopedAStatus upgradeKey(const vector& keyBlobToUpgrade, + const vector& upgradeParams, + vector* keyBlob) override; + + ScopedAStatus deleteKey(const vector& keyBlob) override; + ScopedAStatus deleteAllKeys() override; + ScopedAStatus destroyAttestationIds() override; + + virtual ScopedAStatus begin(KeyPurpose in_purpose, const std::vector& in_keyBlob, + const std::vector& in_params, + const std::optional& in_authToken, + BeginResult* _aidl_return) override; + + ScopedAStatus deviceLocked(bool passwordOnly, + const optional& timestampToken) override; + + ScopedAStatus earlyBootEnded() override; + + ScopedAStatus getKeyCharacteristics(const std::vector& in_keyBlob, + const std::vector& in_appId, + const std::vector& in_appData, + std::vector* _aidl_return) override; + + ScopedAStatus convertStorageKeyToEphemeral(const std::vector& storageKeyBlob, + std::vector* ephemeralKeyBlob) override; + + ScopedAStatus getRootOfTrustChallenge(array* challenge) override; + + ScopedAStatus getRootOfTrust(const array& challenge, + vector* rootOfTrust) override; + + ScopedAStatus sendRootOfTrust(const vector& rootOfTrust) override; + + private: + keymaster_error_t parseWrappedKey(const vector& wrappedKeyData, + std::vector& iv, std::vector& transitKey, + std::vector& secureKey, std::vector& tag, + vector& authList, KeyFormat& keyFormat, + std::vector& wrappedKeyDescription); + + std::tuple, keymaster_error_t> sendBeginImportWrappedKeyCmd( + const std::vector& transitKey, const std::vector& wrappingKeyBlob, + const std::vector& maskingKey, const vector& unwrappingParams); + + std::tuple, keymaster_error_t> + sendFinishImportWrappedKeyCmd(const vector& keyParams, KeyFormat keyFormat, + const std::vector& secureKey, + const std::vector& tag, const std::vector& iv, + const std::vector& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid); + + ScopedAStatus defaultHwInfo(KeyMintHardwareInfo* info); + + const SecurityLevel securitylevel_; + const shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp new file mode 100644 index 0000000..a46f066 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" + +#include "JavacardKeyMintOperation.h" + +#include +#include +#include +#include + +#include "CborConverter.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::Uint; +using secureclock::TimeStampToken; + +JavacardKeyMintOperation::~JavacardKeyMintOperation() { + if (opHandle_ != 0) { + JavacardKeyMintOperation::abort(); + } +} + +ScopedAStatus JavacardKeyMintOperation::updateAad(const vector& input, + const optional& authToken, + const optional& timestampToken) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken.value_or(HardwareAuthToken())); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_AAD_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintOperation::update(const vector& input, + const optional& authToken, + const optional& timestampToken, + vector* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + DataView view = {.buffer = {}, .data = input, .start = 0, .length = input.size()}; + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + if (view.length > MAX_CHUNK_SIZE) { + err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + vector remaining = popNextChunk(view, view.length); + err = sendUpdate(remaining, aToken, tToken, *output); + } + return km_utils::kmError2ScopedAStatus(err); +} + +ScopedAStatus JavacardKeyMintOperation::finish(const optional>& input, + const optional>& signature, + const optional& authToken, + const optional& timestampToken, + const optional>& confirmationToken, + vector* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + const vector confToken = confirmationToken.value_or(vector()); + const vector inData = input.value_or(vector()); + DataView view = {.buffer = {}, .data = inData, .start = 0, .length = inData.size()}; + const vector sign = signature.value_or(vector()); + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + appendBufferedData(view); + if (view.length > MAX_CHUNK_SIZE) { + auto err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + } else { + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + appendBufferedData(view); + } + vector remaining = popNextChunk(view, view.length); + return km_utils::kmError2ScopedAStatus( + sendFinish(remaining, sign, aToken, tToken, confToken, *output)); +} + +ScopedAStatus JavacardKeyMintOperation::abort() { + Array request; + request.add(Uint(opHandle_)); + auto [item, err] = card_->sendRequest(Instruction::INS_ABORT_OPERATION_CMD, request); + opHandle_ = 0; + buffer_.clear(); + return km_utils::kmError2ScopedAStatus(err); +} + +void JavacardKeyMintOperation::blockAlign(DataView& view, uint16_t blockSize) { + appendBufferedData(view); + uint16_t offset = getDataViewOffset(view, blockSize); + if (view.buffer.empty() && !view.data.empty()) { + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } else if (view.data.empty() && !view.buffer.empty()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + } else { + if (offset < view.buffer.size()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + } else { + offset = offset - view.buffer.size(); + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } + } + // adjust the view length by removing the buffered data size from it. + view.length = view.length - buffer_.size(); +} + +uint16_t JavacardKeyMintOperation::getDataViewOffset(DataView& view, uint16_t blockSize) { + uint16_t offset = 0; + uint16_t remaining = 0; + switch (bufferingMode_) { + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + remaining = (view.length % blockSize); + if (offset >= blockSize && remaining == 0) { + offset -= blockSize; + } + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + if (view.length > macLength_) { + offset = (view.length - macLength_); + } + break; + default: + break; + } + return offset; +} + +keymaster_error_t JavacardKeyMintOperation::bufferData(DataView& view) { + if (view.data.empty()) return KM_ERROR_OK; // nothing to buffer + switch (bufferingMode_) { + case BufferingMode::RSA_DECRYPT_OR_NO_DIGEST: + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + if (buffer_.size() > RSA_BUFFER_SIZE) { + abort(); + return KM_ERROR_INVALID_INPUT_LENGTH; + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::EC_NO_DIGEST: + if (buffer_.size() < EC_BUFFER_SIZE) { + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + // Truncate the buffered data if greater then allowed EC buffer size. + if (buffer_.size() > EC_BUFFER_SIZE) { + buffer_.erase(buffer_.begin() + EC_BUFFER_SIZE, buffer_.end()); + } + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, AES_BLOCK_SIZE); + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + blockAlign(view, macLength_); + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, DES_BLOCK_SIZE); + break; + case BufferingMode::NONE: + break; + } + return KM_ERROR_OK; +} + +// Incrementally send the request using multiple updates. +keymaster_error_t JavacardKeyMintOperation::updateInChunks(DataView& view, + HardwareAuthToken& authToken, + TimeStampToken& timestampToken, + vector* output) { + keymaster_error_t sendError = KM_ERROR_UNKNOWN_ERROR; + while (view.length > MAX_CHUNK_SIZE) { + vector chunk = popNextChunk(view, MAX_CHUNK_SIZE); + sendError = sendUpdate(chunk, authToken, timestampToken, *output); + if (sendError != KM_ERROR_OK) { + return sendError; + } + // Clear tokens + if (!authToken.mac.empty()) authToken = HardwareAuthToken(); + if (!timestampToken.mac.empty()) timestampToken = TimeStampToken(); + } + return KM_ERROR_OK; +} + +vector JavacardKeyMintOperation::popNextChunk(DataView& view, uint32_t chunkSize) { + uint32_t start = view.start; + uint32_t end = start + ((view.length < chunkSize) ? view.length : chunkSize); + vector chunk; + if (start < view.buffer.size()) { + if (end < view.buffer.size()) { + chunk = {view.buffer.begin() + start, view.buffer.begin() + end}; + } else { + end = end - view.buffer.size(); + chunk = {view.buffer.begin() + start, view.buffer.end()}; + chunk.insert(chunk.end(), view.data.begin(), view.data.begin() + end); + } + } else { + start = start - view.buffer.size(); + end = end - view.buffer.size(); + chunk = {view.data.begin() + start, view.data.begin() + end}; + } + view.start = view.start + chunk.size(); + view.length = view.length - chunk.size(); + return chunk; +} + +keymaster_error_t JavacardKeyMintOperation::sendUpdate(const vector& input, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + vector& output) { + if (input.empty()) { + return KM_ERROR_OK; + } + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + auto [item, error] = card_->sendRequest(Instruction::INS_UPDATE_OPERATION_CMD, request); + if (error != KM_ERROR_OK) { + return error; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector& data, + const vector& sign, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector& confToken, + vector& output) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(data)); + request.add(Bstr(sign)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + request.add(Bstr(confToken)); + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return err; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + opHandle_ = 0; + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h new file mode 100644 index 0000000..c1d967a --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h @@ -0,0 +1,136 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +#define AES_BLOCK_SIZE 16 +#define DES_BLOCK_SIZE 8 +#define RSA_BUFFER_SIZE 256 +#define EC_BUFFER_SIZE 32 +#define MAX_CHUNK_SIZE 256 + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::Instruction; +using ::keymint::javacard::JavacardSecureElement; +using ::ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::optional; +using std::shared_ptr; +using std::vector; + +// Bufferig modes for update +enum class BufferingMode : int32_t { + NONE = 0, // Send everything to javacard - most of the assymteric operations + RSA_DECRYPT_OR_NO_DIGEST = + 1, // Buffer everything in update upto 256 bytes and send in finish. If + // input data is greater then 256 bytes then it is an error. Javacard + // will further check according to exact key size and crypto provider. + EC_NO_DIGEST = 2, // Buffer upto 65 bytes and then truncate. Javacard will further truncate + // upto exact keysize. + BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 3, // Buffer 16 bytes. + BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED = 4, // Buffer 16 bytes. + BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 5, // Buffer 8 bytes. + BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED = 6, // Buffer 8 bytes. + BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED = 7, // Buffer 16 bytes. + +}; + +// The is the view in the input data being processed by update/finish funcion. + +struct DataView { + vector buffer; // previously buffered data from cycle n-1 + const vector& data; // current data in cycle n. + uint32_t start; // start of the view + size_t length; // length of the view +}; + +class JavacardKeyMintOperation : public BnKeyMintOperation { + public: + explicit JavacardKeyMintOperation(keymaster_operation_handle_t opHandle, + BufferingMode bufferingMode, uint16_t macLength, + shared_ptr card) + : buffer_(vector()), bufferingMode_(bufferingMode), macLength_(macLength), + card_(card), opHandle_(opHandle) {} + virtual ~JavacardKeyMintOperation(); + + ScopedAStatus updateAad(const vector& input, + const optional& authToken, + const optional& timestampToken) override; + + ScopedAStatus update(const vector& input, const optional& authToken, + const optional& timestampToken, + vector* output) override; + + ScopedAStatus finish(const optional>& input, + const optional>& signature, + const optional& authToken, + const optional& timestampToken, + const optional>& confirmationToken, + vector* output) override; + + ScopedAStatus abort() override; + + private: + vector popNextChunk(DataView& view, uint32_t chunkSize); + + keymaster_error_t updateInChunks(DataView& data, HardwareAuthToken& authToken, + TimeStampToken& timestampToken, vector* output); + + keymaster_error_t sendFinish(const vector& data, const vector& signature, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector& confToken, vector& output); + + keymaster_error_t sendUpdate(const vector& data, const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, vector& output); + + inline void appendBufferedData(DataView& view) { + if (!buffer_.empty()) { + view.buffer = buffer_; + view.length = view.length + buffer_.size(); + view.start = 0; + // view.buffer = insert(data.begin(), buffer_.begin(), buffer_.end()); + buffer_.clear(); + } + } + + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + keymaster_error_t bufferData(DataView& data); + void blockAlign(DataView& data, uint16_t blockSize); + uint16_t getDataViewOffset(DataView& view, uint16_t blockSize); + + vector buffer_; + BufferingMode bufferingMode_; + uint16_t macLength_; + const shared_ptr card_; + keymaster_operation_handle_t opHandle_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp new file mode 100644 index 0000000..c79889f --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.rkp.strongbox-impl" + +#include "JavacardRemotelyProvisionedComponentDevice.h" + +#include + +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::EncodedItem; +using cppcose::kCoseMac0EntryCount; +using cppcose::kCoseMac0Payload; +using ::keymint::javacard::Instruction; +using std::string; + +// RKP error codes defined in keymint applet. +constexpr int32_t kStatusFailed = 32000; +constexpr int32_t kStatusInvalidMac = 32001; +constexpr int32_t kStatusProductionKeyInTestRequest = 32002; +constexpr int32_t kStatusTestKeyInProductionRequest = 32003; +constexpr int32_t kStatusInvalidEek = 32004; +constexpr int32_t kStatusInvalidState = 32005; + +namespace { + +keymaster_error_t translateRkpErrorCode(keymaster_error_t error) { + switch (static_cast(-error)) { + case kStatusFailed: + case kStatusInvalidState: + return static_cast(BnRemotelyProvisionedComponent::STATUS_FAILED); + case kStatusInvalidMac: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); + case kStatusProductionKeyInTestRequest: + return static_cast( + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + case kStatusTestKeyInProductionRequest: + return static_cast( + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + case kStatusInvalidEek: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } + return error; +} + +ScopedAStatus defaultHwInfo(RpcHardwareInfo* info) { + info->versionNumber = 3; + info->rpcAuthorName = "Google"; + info->supportedEekCurve = RpcHardwareInfo::CURVE_NONE; + info->uniqueId = "Google Strongbox KeyMint 3"; + info->supportedNumKeysInCsr = RpcHardwareInfo::MIN_SUPPORTED_NUM_KEYS_IN_CSR; + return ScopedAStatus::ok(); +} + +uint32_t coseKeyEncodedSize(const std::vector& keysToSign) { + uint32_t size = 0; + for (auto& macKey : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(macKey.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + LOG(ERROR) << "Invalid COSE_Mac0 structure"; + return 0; + } + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + if (!payload) return 0; + size += payload->value().size(); + } + return size; +} + +} // namespace + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RKP_HARDWARE_INFO); + std::optional optVersionNumber; + std::optional optSupportedEekCurve; + std::optional optRpcAuthorName; + std::optional optUniqueId; + std::optional optMinSupportedKeysInCsr; + if (err != KM_ERROR_OK || !(optVersionNumber = cbor_.getUint64(item, 1)) || + !(optRpcAuthorName = cbor_.getByteArrayStr(item, 2)) || + !(optSupportedEekCurve = cbor_.getUint64(item, 3)) || + !(optUniqueId = cbor_.getByteArrayStr(item, 4)) || + !(optMinSupportedKeysInCsr = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + info->rpcAuthorName = std::move(optRpcAuthorName.value()); + info->versionNumber = static_cast(std::move(optVersionNumber.value())); + info->supportedEekCurve = static_cast(std::move(optSupportedEekCurve.value())); + info->uniqueId = std::move(optUniqueId.value()); + info->supportedNumKeysInCsr = static_cast(std::move(optMinSupportedKeysInCsr.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateEcdsaP256KeyPair( + bool testMode, MacedPublicKey* macedPublicKey, std::vector* privateKeyHandle) { + cppbor::Array array; + array.add(testMode); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_RKP_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + std::optional> optMacedKey; + std::optional> optPKeyHandle; + if (!(optMacedKey = cbor_.getByteArrayVec(item, 1)) || + !(optPKeyHandle = cbor_.getByteArrayVec(item, 2))) { + LOG(ERROR) << "Error in decoding the response in generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *privateKeyHandle = std::move(optPKeyHandle.value()); + macedPublicKey->macedKey = std::move(optMacedKey.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::beginSendData( + const std::vector& keysToSign, const std::vector& challenge, + DeviceInfo* deviceInfo, uint32_t* version, std::string* certificateType) { + uint32_t totalEncodedSize = coseKeyEncodedSize(keysToSign); + cppbor::Array array; + array.add(keysToSign.size()); + array.add(totalEncodedSize); + array.add(challenge); + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_SEND_DATA_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in beginSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDecodedDeviceInfo = cbor_.getByteArrayVec(item, 1); + if (!optDecodedDeviceInfo) { + LOG(ERROR) << "Error in decoding deviceInfo response in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + deviceInfo->deviceInfo = std::move(optDecodedDeviceInfo.value()); + auto optVersion = cbor_.getUint64(item, 2); + if (!optVersion) { + LOG(ERROR) << "Error in decoding version in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *version = optVersion.value(); + auto optCertType = cbor_.getTextStr(item, 3); + if (!optCertType) { + LOG(ERROR) << "Error in decoding cert type in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *certificateType = std::move(optCertType.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateMacedKey( + const std::vector& keysToSign, Array& coseKeys) { + for (auto& macedPublicKey : keysToSign) { + cppbor::Array array; + array.add(EncodedItem(macedPublicKey.macedKey)); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateMacedKey."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto coseKeyData = cbor_.getByteArrayVec(item, 1); + coseKeys.add(EncodedItem(coseKeyData.value())); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::finishSendData( + std::vector& coseEncryptProtectedHeader, std::vector& signature, + uint32_t& version, uint32_t& respFlag) { + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_SEND_DATA_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in finishSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optCEncryptProtectedHeader = cbor_.getByteArrayVec(item, 1); + auto optSignature = cbor_.getByteArrayVec(item, 2); + auto optVersion = cbor_.getUint64(item, 3); + auto optRespFlag = cbor_.getUint64(item, 4); + if (!optCEncryptProtectedHeader || !optSignature || !optVersion || !optRespFlag) { + LOG(ERROR) << "Error in decoding response in finishSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + + coseEncryptProtectedHeader = std::move(optCEncryptProtectedHeader.value()); + signature.insert(signature.end(), optSignature->begin(), optSignature->end()); + version = std::move(optVersion.value()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getDiceCertChain(std::vector& diceCertChain) { + uint32_t respFlag = 0; + do { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_DICE_CERT_CHAIN_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getDiceCertChain."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDiceCertChain = cbor_.getByteArrayVec(item, 1); + auto optRespFlag = cbor_.getUint64(item, 2); + if (!optDiceCertChain || !optRespFlag) { + LOG(ERROR) << "Error in decoding response in getDiceCertChain."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + respFlag = optRespFlag.value(); + diceCertChain.insert(diceCertChain.end(), optDiceCertChain->begin(), + optDiceCertChain->end()); + } while (respFlag != 0); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getUdsCertsChain(std::vector& udsCertsChain) { + uint32_t respFlag = 0; + do { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_UDS_CERTS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getUdsCertsChain."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optUdsCertData = cbor_.getByteArrayVec(item, 1); + auto optRespFlag = cbor_.getUint64(item, 2); + if (!optUdsCertData || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in getUdsCertsChain."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + respFlag = optRespFlag.value(); + udsCertsChain.insert(udsCertsChain.end(), optUdsCertData->begin(), optUdsCertData->end()); + } while (respFlag != 0); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequest( + bool, const std::vector&, const std::vector&, + const std::vector&, DeviceInfo*, ProtectedData*, std::vector*) { + return km_utils::kmError2ScopedAStatus(static_cast(STATUS_REMOVED)); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequestV2( + const std::vector& keysToSign, const std::vector& challenge, + std::vector* csr) { + uint32_t version; + uint32_t csrPayloadSchemaVersion; + std::string certificateType; + uint32_t respFlag; + DeviceInfo deviceInfo; + Array coseKeys; + std::vector protectedHeader; + cppbor::Map coseEncryptUnProtectedHeader; + std::vector signature; + std::vector diceCertChain; + std::vector udsCertChain; + cppbor::Array payLoad; + + auto ret = beginSendData(keysToSign, challenge, &deviceInfo, &csrPayloadSchemaVersion, + &certificateType); + if (!ret.isOk()) return ret; + + ret = updateMacedKey(keysToSign, coseKeys); + if (!ret.isOk()) return ret; + + ret = finishSendData(protectedHeader, signature, version, respFlag); + if (!ret.isOk()) return ret; + + ret = getUdsCertsChain(udsCertChain); + if (!ret.isOk()) return ret; + + ret = getDiceCertChain(diceCertChain); + if (!ret.isOk()) return ret; + + auto payload = cppbor::Array() + .add(csrPayloadSchemaVersion) + .add(certificateType) + .add(EncodedItem(deviceInfo.deviceInfo)) // deviceinfo + .add(std::move(coseKeys)) // KeysToSign + .encode(); + + auto signDataPayload = cppbor::Array() + .add(challenge) // Challenge + .add(std::move(payload)) + .encode(); + + auto signedData = cppbor::Array() + .add(std::move(protectedHeader)) + .add(cppbor::Map() /* unprotected parameters */) + .add(std::move(signDataPayload)) + .add(std::move(signature)); + + *csr = cppbor::Array() + .add(version) + .add(EncodedItem(udsCertChain)) + .add(EncodedItem(diceCertChain)) + .add(std::move(signedData)) + .encode(); + + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h new file mode 100644 index 0000000..5ce8cd7 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h @@ -0,0 +1,80 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include +#include +#include + +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; + +class JavacardRemotelyProvisionedComponentDevice : public BnRemotelyProvisionedComponent { + public: + explicit JavacardRemotelyProvisionedComponentDevice(shared_ptr card) + : card_(card) {} + + virtual ~JavacardRemotelyProvisionedComponentDevice() = default; + + ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override; + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector& keysToSign, + const std::vector& endpointEncCertChain, + const std::vector& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, + std::vector* keysToSignMac) override; + + ScopedAStatus generateCertificateRequestV2(const std::vector& keysToSign, + const std::vector& challenge, + std::vector* csr) override; + + private: + ScopedAStatus beginSendData(const std::vector& keysToSign, + const std::vector& challenge, DeviceInfo* deviceInfo, + uint32_t* version, std::string* certificateType); + + ScopedAStatus updateMacedKey(const std::vector& keysToSign, + cppbor::Array& coseKeys); + + ScopedAStatus finishSendData(std::vector& coseEncryptProtectedHeader, + std::vector& signature, uint32_t& version, + uint32_t& respFlag); + + ScopedAStatus getResponse(std::vector& partialCipheredData, + cppbor::Array& recepientStructure, uint32_t& respFlag); + ScopedAStatus getDiceCertChain(std::vector& diceCertChain); + ScopedAStatus getUdsCertsChain(std::vector& udsCertsChain); + std::shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp new file mode 100644 index 0000000..7c4f038 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" +#include "JavacardSecureElement.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "keymint_utils.h" + +namespace keymint::javacard { + +keymaster_error_t JavacardSecureElement::initializeJavacard() { + Array request; + request.add(Uint(getOsVersion())); + request.add(Uint(getOsPatchlevel())); + request.add(Uint(getVendorPatchlevel())); + auto [item, err] = sendRequest(Instruction::INS_INIT_STRONGBOX_CMD, request); + return err; +} + +keymaster_error_t JavacardSecureElement::sendEarlyBootEndedEvent(bool eventTriggered) { + isEarlyBootEventPending |= eventTriggered; + if (!isEarlyBootEventPending) { + return KM_ERROR_OK; + } + auto [item, err] = sendRequest(Instruction::INS_EARLY_BOOT_ENDED_CMD); + if (err != KM_ERROR_OK) { + // Incase of failure cache the event and send in the next immediate request to Applet. + isEarlyBootEventPending = true; + return err; + } + isEarlyBootEventPending = false; + return KM_ERROR_OK; +} + +keymaster_error_t JavacardSecureElement::constructApduMessage(Instruction& ins, + std::vector& inputData, + std::vector& apduOut) { + apduOut.push_back(static_cast(APDU_CLS)); // CLS + apduOut.push_back(static_cast(ins)); // INS + apduOut.push_back(static_cast(APDU_P1)); // P1 + apduOut.push_back(static_cast(APDU_P2)); // P2 + + if (USHRT_MAX >= inputData.size()) { + // Send extended length APDU always as response size is not known to HAL. + // Case 1: Lc > 0 CLS | INS | P1 | P2 | 00 | 2 bytes of Lc | CommandData | 2 bytes of Le + // all set to 00. Case 2: Lc = 0 CLS | INS | P1 | P2 | 3 bytes of Le all set to 00. + // Extended length 3 bytes, starts with 0x00 + apduOut.push_back(static_cast(0x00)); + if (inputData.size() > 0) { + apduOut.push_back(static_cast(inputData.size() >> 8)); + apduOut.push_back(static_cast(inputData.size() & 0xFF)); + // Data + apduOut.insert(apduOut.end(), inputData.begin(), inputData.end()); + } + // Expected length of output. + // Accepting complete length of output every time. + apduOut.push_back(static_cast(0x00)); + apduOut.push_back(static_cast(0x00)); + } else { + LOG(ERROR) << "Error in constructApduMessage."; + return (KM_ERROR_INVALID_INPUT_LENGTH); + } + return (KM_ERROR_OK); // success +} + +keymaster_error_t JavacardSecureElement::sendData(Instruction ins, std::vector& inData, + std::vector& response) { + keymaster_error_t ret = KM_ERROR_UNKNOWN_ERROR; + std::vector apdu; + + ret = constructApduMessage(ins, inData, apdu); + + if (ret != KM_ERROR_OK) { + return ret; + } + + ret = transport_->sendData(apdu, response); + if (ret != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in sendData. " << static_cast(ret); + return ret; + } + + // Response size should be greater than 2. Cbor output data followed by two bytes of APDU + // status. + if ((response.size() <= 2) || (getApduStatus(response) != APDU_RESP_STATUS_OK)) { + LOG(ERROR) << "Response of the sendData is wrong: response size = " << response.size() + << " apdu status = " << getApduStatus(response); + return (KM_ERROR_UNKNOWN_ERROR); + } + // remove the status bytes + response.pop_back(); + response.pop_back(); + return (KM_ERROR_OK); // success +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, Array& request) { + vector response; + // encode request + std::vector command = request.encode(); + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, std::vector& command) { + vector response; + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins) { + vector response; + vector emptyRequest; + auto sendError = sendData(ins, emptyRequest, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h new file mode 100644 index 0000000..8ba0a44 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h @@ -0,0 +1,116 @@ +/* + * Copyright 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. + */ + +#pragma once + +#include + +#include "CborConverter.h" + +#define APDU_CLS 0x80 +#define APDU_P1 0x60 +#define APDU_P2 0x00 +#define APDU_RESP_STATUS_OK 0x9000 + +#define KEYMINT_CMD_APDU_START 0x20 + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +enum class Instruction { + // Keymaster commands + INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1, + INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2, + INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3, + INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4, + INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5, + INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6, + INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7, + INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8, + INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9, + INS_COMPUTE_SHARED_SECRET_CMD = KEYMINT_CMD_APDU_START + 10, + INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11, + INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12, + INS_GET_SHARED_SECRET_PARAM_CMD = KEYMINT_CMD_APDU_START + 13, + INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14, + INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15, + INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16, + INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17, + INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18, + INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19, + INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20, + INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21, + INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22, + INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23, + INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24, + INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25, + INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26, + // RKP Commands + INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27, + INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28, + INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29, + INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30, + INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31, + INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32, + INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33, + INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34, + INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35, + INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36, + // SE ROT Commands + INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45, + INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46, + INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47, +}; + +class JavacardSecureElement { + public: + explicit JavacardSecureElement(shared_ptr transport, uint32_t osVersion, + uint32_t osPatchLevel, uint32_t vendorPatchLevel) + : transport_(transport), osVersion_(osVersion), osPatchLevel_(osPatchLevel), + vendorPatchLevel_(vendorPatchLevel), isEarlyBootEventPending(false) { + transport_->openConnection(); + } + virtual ~JavacardSecureElement() { transport_->closeConnection(); } + + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + std::tuple, keymaster_error_t> sendRequest(Instruction ins); + std::tuple, keymaster_error_t> sendRequest(Instruction ins, + std::vector& command); + + keymaster_error_t sendData(Instruction ins, std::vector& inData, + std::vector& response); + + keymaster_error_t constructApduMessage(Instruction& ins, std::vector& inputData, + std::vector& apduOut); + keymaster_error_t initializeJavacard(); + keymaster_error_t sendEarlyBootEndedEvent(bool eventTriggered); + inline uint16_t getApduStatus(std::vector& inputData) { + // Last two bytes are the status SW0SW1 + uint8_t SW0 = inputData.at(inputData.size() - 2); + uint8_t SW1 = inputData.at(inputData.size() - 1); + return (SW0 << 8 | SW1); + } + + shared_ptr transport_; + uint32_t osVersion_; + uint32_t osPatchLevel_; + uint32_t vendorPatchLevel_; + bool isEarlyBootEventPending; + CborConverter cbor_; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp new file mode 100644 index 0000000..c5cf9a2 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp @@ -0,0 +1,61 @@ +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" +#include "JavacardSharedSecret.h" + +#include + +#include + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::Instruction; + +ScopedAStatus JavacardSharedSecret::getSharedSecretParameters(SharedSecretParameters* params) { + auto error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + auto [item, err] = card_->sendRequest(Instruction::INS_GET_SHARED_SECRET_PARAM_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSSParams = cbor_.getSharedSecretParameters(item, 1); + if (!optSSParams) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *params = std::move(optSSParams.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardSharedSecret::computeSharedSecret(const std::vector& params, + std::vector* secret) { + + auto error = card_->sendEarlyBootEndedEvent(false); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBoot event javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + cppbor::Array request; + cbor_.addSharedSecretParameters(request, params); + auto [item, err] = card_->sendRequest(Instruction::INS_COMPUTE_SHARED_SECRET_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSecret = cbor_.getByteArrayVec(item, 1); + if (!optSecret) { + LOG(ERROR) << "Error in decoding the response in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *secret = std::move(optSecret.value()); + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h new file mode 100644 index 0000000..340853a --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; +using std::vector; + +class JavacardSharedSecret : public BnSharedSecret { + public: + explicit JavacardSharedSecret(shared_ptr card) : card_(card) {} + virtual ~JavacardSharedSecret() {} + + ScopedAStatus getSharedSecretParameters(SharedSecretParameters* params) override; + + ScopedAStatus computeSharedSecret(const std::vector& params, + std::vector* secret) override; + + private: + shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM300/HAL/LICENSE b/ready_se/google/keymint/KM300/HAL/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/ready_se/google/keymint/KM300/HAL/METADATA b/ready_se/google/keymint/KM300/HAL/METADATA new file mode 100644 index 0000000..d97975c --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/ready_se/google/keymint/KM300/HAL/OWNERS b/ready_se/google/keymint/KM300/HAL/OWNERS new file mode 100644 index 0000000..0bd972b --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/OWNERS @@ -0,0 +1,3 @@ +pathakc@google.com +subrahmanyaman@google.com +avinashh@google.com diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml new file mode 100644 index 0000000..ca49e71 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc new file mode 100644 index 0000000..e1c1494 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc @@ -0,0 +1,3 @@ +service vendor.keymint-strongbox /vendor/bin/hw/android.hardware.security.keymint3-service.strongbox + class early_hal + user jc_strongbox diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml new file mode 100644 index 0000000..481f028 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml @@ -0,0 +1,12 @@ + + + android.hardware.security.keymint + 3 + IKeyMintDevice/strongbox + + + android.hardware.security.keymint + 3 + IRemotelyProvisionedComponent/strongbox + + diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml new file mode 100644 index 0000000..5492100 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml @@ -0,0 +1,6 @@ + + + android.hardware.security.sharedsecret + ISharedSecret/strongbox + + diff --git a/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp b/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp new file mode 100644 index 0000000..f613eda --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp @@ -0,0 +1,130 @@ +/* + * 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. + */ +#include "keymint_utils.h" + +#include + +#include + +namespace keymint::javacard { + +namespace { + +constexpr char kPlatformVersionProp[] = "ro.build.version.release"; +constexpr char kPlatformVersionRegex[] = "^([0-9]{1,2})(\\.([0-9]{1,2}))?(\\.([0-9]{1,2}))?"; +constexpr size_t kMajorVersionMatch = 1; +constexpr size_t kMinorVersionMatch = 3; +constexpr size_t kSubminorVersionMatch = 5; +constexpr size_t kPlatformVersionMatchCount = kSubminorVersionMatch + 1; + +constexpr char kPlatformPatchlevelProp[] = "ro.build.version.security_patch"; +constexpr char kVendorPatchlevelProp[] = "ro.vendor.build.security_patch"; +constexpr char kPatchlevelRegex[] = "^([0-9]{4})-([0-9]{2})-([0-9]{2})$"; +constexpr size_t kYearMatch = 1; +constexpr size_t kMonthMatch = 2; +constexpr size_t kDayMatch = 3; +constexpr size_t kPatchlevelMatchCount = kDayMatch + 1; + +uint32_t match_to_uint32(const char* expression, const regmatch_t& match) { + if (match.rm_so == -1) return 0; + + size_t len = match.rm_eo - match.rm_so; + std::string s(expression + match.rm_so, len); + return std::stoul(s); +} + +std::string wait_and_get_property(const char* prop) { + std::string prop_value; + while (!::android::base::WaitForPropertyCreation(prop)) + ; + prop_value = ::android::base::GetProperty(prop, "" /* default */); + return prop_value; +} + +uint32_t getOsVersion(const char* version_str) { + regex_t regex; + if (regcomp(®ex, kPlatformVersionRegex, REG_EXTENDED)) { + return 0; + } + + regmatch_t matches[kPlatformVersionMatchCount]; + int not_match = + regexec(®ex, version_str, kPlatformVersionMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t major = match_to_uint32(version_str, matches[kMajorVersionMatch]); + uint32_t minor = match_to_uint32(version_str, matches[kMinorVersionMatch]); + uint32_t subminor = match_to_uint32(version_str, matches[kSubminorVersionMatch]); + + return (major * 100 + minor) * 100 + subminor; +} + +enum class PatchlevelOutput { kYearMonthDay, kYearMonth }; + +uint32_t getPatchlevel(const char* patchlevel_str, PatchlevelOutput detail) { + regex_t regex; + if (regcomp(®ex, kPatchlevelRegex, REG_EXTENDED) != 0) { + return 0; + } + + regmatch_t matches[kPatchlevelMatchCount]; + int not_match = regexec(®ex, patchlevel_str, kPatchlevelMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t year = match_to_uint32(patchlevel_str, matches[kYearMatch]); + uint32_t month = match_to_uint32(patchlevel_str, matches[kMonthMatch]); + + if (month < 1 || month > 12) { + return 0; + } + + switch (detail) { + case PatchlevelOutput::kYearMonthDay: { + uint32_t day = match_to_uint32(patchlevel_str, matches[kDayMatch]); + if (day < 1 || day > 31) { + return 0; + } + return year * 10000 + month * 100 + day; + } + case PatchlevelOutput::kYearMonth: + return year * 100 + month; + } +} + +} // anonymous namespace + +uint32_t getOsVersion() { + std::string version = wait_and_get_property(kPlatformVersionProp); + return getOsVersion(version.c_str()); +} + +uint32_t getOsPatchlevel() { + std::string patchlevel = wait_and_get_property(kPlatformPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonth); +} + +uint32_t getVendorPatchlevel() { + std::string patchlevel = wait_and_get_property(kVendorPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonthDay); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/keymint_utils.h b/ready_se/google/keymint/KM300/HAL/keymint_utils.h new file mode 100644 index 0000000..65cda63 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/keymint_utils.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +// #include + +// namespace aidl::android::hardware::security::keymint { +namespace keymint::javacard { + +using std::vector; + +inline static std::vector blob2vector(const uint8_t* data, const size_t length) { + std::vector result(data, data + length); + return result; +} + +inline static std::vector blob2vector(const std::string& value) { + vector result(reinterpret_cast(value.data()), + reinterpret_cast(value.data()) + value.size()); + return result; +} + +// HardwareAuthToken vector2AuthToken(const vector& buffer); +// vector authToken2vector(const HardwareAuthToken& token); + +uint32_t getOsVersion(); +uint32_t getOsPatchlevel(); +uint32_t getVendorPatchlevel(); + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/service.cpp b/ready_se/google/keymint/KM300/HAL/service.cpp new file mode 100644 index 0000000..e83ee3d --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/service.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "javacard.strongbox-service" + +#include + +#include +#include +#include +#include + +#include "JavacardKeyMintDevice.h" +#include "JavacardRemotelyProvisionedComponentDevice.h" +#include "JavacardSecureElement.h" +#include "JavacardSharedSecret.h" +#include "OmapiTransport.h" +#include "SocketTransport.h" +#include "keymint_utils.h" + +using aidl::android::hardware::security::keymint::JavacardKeyMintDevice; +using aidl::android::hardware::security::keymint::JavacardRemotelyProvisionedComponentDevice; +using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::sharedsecret::JavacardSharedSecret; +using keymint::javacard::getOsPatchlevel; +using keymint::javacard::getOsVersion; +using keymint::javacard::getVendorPatchlevel; +using keymint::javacard::ITransport; +using keymint::javacard::JavacardSecureElement; +using keymint::javacard::OmapiTransport; +using keymint::javacard::SocketTransport; + +#define PROP_BUILD_QEMU "ro.kernel.qemu" +#define PROP_BUILD_FINGERPRINT "ro.build.fingerprint" +// Cuttlefish build fingerprint substring. +#define CUTTLEFISH_FINGERPRINT_SS "aosp_cf_" + +template std::shared_ptr addService(Args&&... args) { + std::shared_ptr ser = ndk::SharedRefBase::make(std::forward(args)...); + auto instanceName = std::string(T::descriptor) + "/strongbox"; + LOG(INFO) << "adding javacard strongbox service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} + +std::shared_ptr getTransportInstance() { + bool isEmulator = false; + // Check if the current build is for emulator or device. + isEmulator = android::base::GetBoolProperty(PROP_BUILD_QEMU, false); + if (!isEmulator) { + std::string fingerprint = android::base::GetProperty(PROP_BUILD_FINGERPRINT, ""); + if (!fingerprint.empty()) { + if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0) != std::string::npos) { + isEmulator = true; + } + } + } + + if (!isEmulator) { + return std::make_shared(); + } else { + return std::make_shared(); + } +} + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + // Javacard Secure Element + std::shared_ptr card = std::make_shared( + getTransportInstance(), getOsVersion(), getOsPatchlevel(), getVendorPatchlevel()); + // Add Keymint Service + addService(card); + // Add Shared Secret Service + addService(card); + // Add Remotely Provisioned Component Service + addService(card); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} -- cgit v1.2.3